From f6e63b55780175eb7722fdc2ed69b1750319570f Mon Sep 17 00:00:00 2001 From: Guido Draheim Date: Mon, 18 Aug 2003 16:04:16 +0000 Subject: [PATCH] This commit was generated by cvs2svn to compensate for changes in r105, which included commits to RCS files with non-trunk default branches. --- zzip/file.c | 998 +++++++++++++++++++++++++++++++++++++++++++++++++++ zzip/write.c | 461 ++++++++++++++++++++++++ zzip/zip.c | 713 ++++++++++++++++++++++++++++++++++++ 3 files changed, 2172 insertions(+) create mode 100644 zzip/file.c create mode 100644 zzip/write.c create mode 100644 zzip/zip.c diff --git a/zzip/file.c b/zzip/file.c new file mode 100644 index 0000000..1c0355b --- /dev/null +++ b/zzip/file.c @@ -0,0 +1,998 @@ +/* + * Author: + * Guido Draheim + * Tomi Ollila + * + * Copyright (c) 1999,2000,2001,2002,2003 Guido Draheim + * All rights reserved, + * use under the restrictions of the + * Lesser GNU General Public License + * or alternatively the restrictions + * of the Mozilla Public License 1.1 + */ + +#include /* exported...*/ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#if 0 +# if defined ZZIP_HAVE_IO_H +# include /* tell */ +# else +# define tell(fd) lseek(fd,0,SEEK_CUR) +# endif +#else +#define tells(fd) seeks(fd,0,SEEK_CUR) +#endif + +/** + * the direct function of => zzip_close(fp). it will cleanup the + * inflate-portion of => zlib and free the structure given. + * + * it is called quite from the error-cleanup parts + * of the various => _open functions. + * + * the .refcount is decreased and if zero the fp->dir is closed just as well. + */ +int +zzip_file_close(ZZIP_FILE * fp) +{ + ZZIP_DIR * dir = fp->dir; + + if (fp->method) + + inflateEnd(&fp->d_stream); /* inflateEnd() can be called many times */ + + if (fp->buf32k) + { + if (dir->cache.buf32k == NULL) dir->cache.buf32k = fp->buf32k; + else free(fp->buf32k); + } + + if (dir->currentfp == fp) + dir->currentfp = NULL; + + dir->refcount--; + /* ease to notice possible dangling reference errors */ + memset(fp, 0, sizeof(*fp)); + + if (dir->cache.fp == NULL) dir->cache.fp = fp; + else free(fp); + + if (! dir->refcount) return zzip_dir_close(dir); else return 0; +} + + +static int +zzip_file_saveoffset(ZZIP_FILE * fp) +{ + if (fp) + { + int fd = fp->dir->fd; + zzip_off_t off = fp->io->fd.seeks(fd, 0, SEEK_CUR); + if (off < 0) + return -1; + + fp->offset = off; + } + return 0; +} + +# ifndef ZZIP_CHECK_BACKSLASH_DIRSEPARATOR /* NOTE: also default */ +# define ZZIP_CHECK_BACKSLASH_DIRSEPARATOR 0 /* to "NO" on win32 ! */ +# endif + +# if !defined strcasecmp && !defined ZZIP_HAVE_STRCASECMP +# define ZZIP_CHECK_BACKSLASH_DIRSEPARATOR 1 +# endif + +#if ! ZZIP_CHECK_BACKSLASH_DIRSEPARATOR+0 +#define dirsep_strrchr(N,C) strrchr(N,C) +#define dirsep_casecmp strcasecmp +#else +#define dirsep_strrchr(N,C) _dirsep_strrchr(N) +#define dirsep_casecmp _dirsep_casecmp +static zzip_char_t* +_dirsep_strrchr (zzip_char_t* name) +{ + char* n = strrchr (name, '/'); + char* m = strrchr (name, '\\'); + if (m && n && m > n) n = m; + return n; +} +static int +_dirsep_casecmp (zzip_char_t* s1, zzip_char_t* s2) +{ + /* ASCII tolower - including mapping of backslash in normal slash */ + static const char mapping[] = "@abcdefghijklmnopqrstuvwxyz[/]^_"; + int c1, c2; + while (*s1 && *s2) + { + c1 = (int)(unsigned char) *s1; + c2 = (int)(unsigned char) *s2; + if ((c1&0xE0) == 0x40) c1 = mapping[c1&0x1f]; + if ((c1&0xE0) == 0x40) c2 = mapping[c2&0x1f]; + if (c1 != c2) + return (c1 - c2); + s1++; s2++; + } + + return (((int)(unsigned char) *s1) - ((int)(unsigned char) *s2)); +} +#endif + +static int zzip_inflate_init(ZZIP_FILE *, struct zzip_dir_hdr *); + +/** + * open an => ZZIP_FILE from an already open => ZZIP_DIR handle. Since + * we have a chance to reuse a cached => buf32k and => ZZIP_FILE memchunk + * this is the best choice to unpack multiple files. + * + * Note: the zlib supports 2..15 bit windowsize, hence we provide a 32k + * memchunk here... just to be safe. + */ +ZZIP_FILE * +zzip_file_open(ZZIP_DIR * dir, zzip_char_t* name, int o_mode) +{ + zzip_error_t err = 0; + struct zzip_file * fp = 0; + struct zzip_dir_hdr * hdr = dir->hdr0; + int (*cmp)(zzip_char_t*, zzip_char_t*); + + cmp = (o_mode & ZZIP_CASELESS)? dirsep_casecmp: strcmp; + + if (! dir || !dir->fd || dir->fd == -1 ) return 0; + + if (o_mode & ZZIP_NOPATHS) + { + register zzip_char_t* n = dirsep_strrchr(name, '/'); + if (n) name = n + 1; + } + + if (hdr) + while (1) + { + register zzip_char_t* hdr_name = hdr->d_name; + if (o_mode & ZZIP_NOPATHS) + { + register zzip_char_t* n = dirsep_strrchr(hdr_name, '/'); + if (n) hdr_name = n + 1; + } + + HINT4("name='%s', compr=%d, size=%d\n", + hdr->d_name, hdr->d_compr, hdr->d_usize); + + if (!cmp(hdr_name, name)) + { + switch (hdr->d_compr) + { + case 0: /* store */ + case 8: /* inflate */ + break; + default: + { err = ZZIP_UNSUPP_COMPR; goto error; } + } + + if (dir->cache.fp) + { + fp = dir->cache.fp; dir->cache.fp = NULL; + /* memset(zfp, 0, sizeof *fp); cleared in zzip_file_close() */ + }else + { + if (! (fp = (ZZIP_FILE *)calloc(1, sizeof(*fp)))) + { err = ZZIP_OUTOFMEM; goto error; } + } + + fp->dir = dir; + fp->io = dir->io; + dir->refcount++; + + if (dir->cache.buf32k) + { fp->buf32k = dir->cache.buf32k; dir->cache.buf32k = NULL; } + else + { + if (! (fp->buf32k = (char *)malloc(ZZIP_32K))) + { err = ZZIP_OUTOFMEM; goto error; } + } + + /* + * In order to support simultaneous open files in one zip archive + * we'll fix the fd offset when opening new file/changing which + * file to read... + */ + + if (zzip_file_saveoffset(dir->currentfp) < 0) + { err = ZZIP_DIR_SEEK; goto error; } + + fp->offset = hdr->d_off; + dir->currentfp = fp; + + if (dir->io->fd.seeks(dir->fd, hdr->d_off, SEEK_SET) < 0) + { err = ZZIP_DIR_SEEK; goto error; } + + { /* skip local header - should test tons of other info, + * but trust that those are correct */ + zzip_ssize_t dataoff; + struct zzip_file_header * p = (void*) fp->buf32k; + + dataoff = dir->io->fd.read(dir->fd, (void*)p, sizeof(*p)); + if (dataoff < (zzip_ssize_t)sizeof(*p)) + { err = ZZIP_DIR_READ; goto error; } + if (! ZZIP_FILE_HEADER_CHECKMAGIC(p)) /* PK\3\4 */ + { err = ZZIP_CORRUPTED; goto error; } + + dataoff = ZZIP_GET16(p->z_namlen) + ZZIP_GET16(p->z_extras); + + if (dir->io->fd.seeks(dir->fd, dataoff, SEEK_CUR) < 0) + { err = ZZIP_DIR_SEEK; goto error; } + + fp->dataoffset = dir->io->fd.tells(dir->fd); + fp->usize = hdr->d_usize; + fp->csize = hdr->d_csize; + } + + err = zzip_inflate_init (fp, hdr); + if (err) { goto error; } + + return fp; + }else + { + if (hdr->d_reclen == 0) + break; + hdr = (struct zzip_dir_hdr *)((char *)hdr + hdr->d_reclen); + }/*cmp name*/ + }/*forever*/ + dir->errcode = ZZIP_ENOENT; zzip_errno(ZZIP_ENOENT); + return NULL; +error: + if (fp) zzip_file_close(fp); + dir->errcode = err; zzip_errno(err); + return NULL; +} + +/** + * call => inflateInit and setup fp's iterator variables, + * used by lowlevel => _open functions. + */ +static int +zzip_inflate_init(ZZIP_FILE * fp, struct zzip_dir_hdr* hdr) +{ + int err; + fp->method = hdr->d_compr; + fp->restlen = hdr->d_usize; + + if (fp->method) + { + memset(&fp->d_stream, 0, sizeof(fp->d_stream)); + + err = inflateInit2(&fp->d_stream, -MAX_WBITS); + if (err != Z_OK) { goto error; } + + fp->crestlen = hdr->d_csize; + } + return 0; +error: + if (fp) zzip_file_close(fp); + return err; +} + +/** + * This function closes the given ZZIP_FILE handle. + * + * If the ZZIP_FILE wraps a normal stat'fd then it is just that int'fd + * that is being closed and the otherwise empty ZZIP_FILE gets freed. + */ +int +zzip_fclose(ZZIP_FILE * fp) +{ + if (! fp) return 0; + if (! fp->dir) + { int r = fp->io->fd.close(fp->fd); free(fp); return r; } /* stat fd */ + else return zzip_file_close(fp); +} + +/** => zzip_fclose + */ +int +zzip_close(ZZIP_FILE* fp) +{ + return zzip_fclose (fp); +} + +/** + * This functions read data from zip-contained file. + * + * It works like => read(2) and will fill the given buffer with bytes from + * the opened file. It will return the number of bytes read, so if the => EOF + * is encountered you will be prompted with the number of bytes actually read. + * + * This is the routines that needs the => buf32k buffer, and it would have + * need for much more polishing but it does already work quite well. + * + * Note: the 32K buffer is rather big. The original inflate-algorithm + * required just that but the latest zlib would work just fine with + * a smaller buffer. + */ +zzip_ssize_t +zzip_file_read(ZZIP_FILE * fp, char * buf, zzip_size_t len) +{ + ZZIP_DIR * dir; + zzip_size_t l; + zzip_ssize_t rv; + + if (! fp || ! fp->dir) return 0; + + dir = fp->dir; + l = fp->restlen > len ? len : fp->restlen; + if (fp->restlen == 0) + return 0; + + /* + * If this is other handle than previous, save current seek pointer + * and read the file position of `this' handle. + */ + if (dir->currentfp != fp) + { + if (zzip_file_saveoffset(dir->currentfp) < 0 + || fp->io->fd.seeks(dir->fd, fp->offset, SEEK_SET) < 0) + { dir->errcode = ZZIP_DIR_SEEK; return -1; } + else + { dir->currentfp = fp; } + } + + /* if more methods is to be supported, change this to `switch ()' */ + if (fp->method) /* method != 0 == 8, inflate */ + { + fp->d_stream.avail_out = l; + fp->d_stream.next_out = (unsigned char *)buf; + + do { + int err; + zzip_size_t startlen; + + if (fp->crestlen > 0 && fp->d_stream.avail_in == 0) + { + zzip_size_t cl = ( fp->crestlen < ZZIP_32K ? + fp->crestlen : ZZIP_32K ); + /* zzip_size_t cl = fp->crestlen > 128 ? 128 : fp->crestlen; */ + + zzip_ssize_t i = fp->io->fd.read(dir->fd, fp->buf32k, cl); + if (i <= 0) + { + dir->errcode = ZZIP_DIR_READ; /* or ZZIP_DIR_READ_EOF ? */ + return -1; + } + fp->crestlen -= i; + fp->d_stream.avail_in = i; + fp->d_stream.next_in = (unsigned char *)fp->buf32k; + } + + startlen = fp->d_stream.total_out; + err = inflate(&fp->d_stream, Z_NO_FLUSH); + + if (err == Z_STREAM_END) + { fp->restlen = 0; } + else + if (err == Z_OK) + { fp->restlen -= (fp->d_stream.total_out - startlen); } + else + { dir->errcode = err; return -1; } + } while (fp->restlen && fp->d_stream.avail_out); + + return l - fp->d_stream.avail_out; + }else + { /* method == 0 -- unstore */ + rv = fp->io->fd.read(dir->fd, buf, l); + if (rv > 0) + { fp->restlen-= rv; } + else + if (rv < 0) + { dir->errcode = ZZIP_DIR_READ; } + return rv; + } +} + +/** + * This function will read(2) data from a real/zipped file. + * + * the replacement for => read(2) will fill the given buffer with bytes from + * the opened file. It will return the number of bytes read, so if the EOF + * is encountered you will be prompted with the number of bytes actually read. + * + * If the file-handle is wrapping a stat'able file then it will actually just + * perform a normal => read(2)-call, otherwise => zzip_file_read is called + * to decompress the data stream and any error is mapped to => errno(3). + */ +zzip_ssize_t +zzip_read(ZZIP_FILE * fp, char * buf, zzip_size_t len) +{ + if (! fp) return 0; + if (! fp->dir) + { return fp->io->fd.read(fp->fd, buf, len); } /* stat fd */ + else + { register zzip_ssize_t v; + v = zzip_file_read(fp, buf, len); + if (v == -1) { errno = zzip_errno(fp->dir->errcode); } + return v; + } +} + +/** => zzip_read + */ +zzip_size_t +zzip_fread(void *ptr, zzip_size_t size, zzip_size_t nmemb, ZZIP_FILE *file) +{ + if (! size) size=1; + return zzip_read (file, ptr, size*nmemb)/size; +} + + +#define ZZIP_WRONLY O_WRONLY +#define ZZIP_EXCL O_EXCL + +#if defined O_SYNC +#define ZZIP_SYNC O_SYNC +#else +#define ZZIP_SYNC 0 +#endif + +#if defined O_NONBLOCK +#define ZZIP_NONBLOCK O_NONBLOCK +#elif defined O_NDELAY +#define ZZIP_NOCTTY O_NDELAY +#else +#define ZZIP_NOCTTY 0 +#endif + +/* ------------------------------------------------------------------- */ + +/** + * This function will => fopen(3) a real/zipped file. + * + * It has some magic functionality builtin - it will first try to open + * the given filename as a normal file. If it does not + * exist, the given path to the filename (if any) is split into + * its directory-part and the file-part. A ".zip" extension is + * then added to the directory-part to create the name of a + * zip-archive. That zip-archive (if it exists) is being searched + * for the file-part, and if found a zzip-handle is returned. + * + * Note that if the file is found in the normal fs-directory the + * returned structure is mostly empty and the => zzip_read call will + * use the libc => read to obtain data. Otherwise a => zzip_file_open + * is performed and any error mapped to => errno(3). + * + * unlike the posix-wrapper => zzip_open the mode-argument is + * a string which allows for more freedom to support the extra + * zzip modes called ZZIP_CASEINSENSITIVE and ZZIP_IGNOREPATH. + * Currently, this => zzip_fopen call will convert the following + * characters in the mode-string into their corrsponding mode-bits: + *
  • "r" : O_RDONLY : read-only + *
  • "b" : O_BINARY : binary (win32 specific) + *
  • "f" : O_NOCTTY : no char device (unix) + *
  • "i" : ZZIP_CASELESS : inside zip file + *
  • "*" : ZZIP_NOPATHS : inside zip file only + *
all other modes will be ignored for zip-contained entries + * but they are transferred for compatibility and portability, + * including these extra sugar bits: + *
  • "x" : O_EXCL : fail if file did exist + *
  • "s" : O_SYNC : synchronized access + *
  • "n" : O_NONBLOCK : nonblocking access + *
  • "z#" : compression level : for zlib + *
  • "g#" : group access : unix access bits + *
  • "u#" : owner access : unix access bits + *
  • "o#" : world access : unix access bits + *
... the access bits are in traditional unix bit format + * with 7 = read/write/execute, 6 = read/write, 4 = read-only. + * + * The default access mode is 0664, and the compression level + * is ignored since the lib can not yet write zip files, otherwise + * it would be the initialisation value for the zlib deflateInit + * where 0 = no-compression, 1 = best-speed, 9 = best-compression. + */ +ZZIP_FILE* +zzip_fopen(zzip_char_t* filename, zzip_char_t* mode) +{ + return zzip_freopen (filename, mode, 0); +} + +/** => zzip_fopen + * + * This function receives an additional argument pointing to + * a ZZIP_FILE* being already in use. If this extra argument is + * null then this function is identical with calling => zzip_fopen + * + * Per default, the old file stream is closed and only the internal + * structures associated with it are kept. These internal structures + * may be reused for the return value, and this is a lot quicker when + * the filename matches a zipped file that is incidently in the very + * same zip arch as the old filename wrapped in the stream struct. + * + * That's simply because the zip arch's central directory does not + * need to be read again. As an extension for this function, if the + * mode-string contains a "q" then the old stream is not closed but + * left untouched, instead it is only given as a hint that a new + * file handle may share/copy the zip arch structures of the old file + * handle if that is possible, i.e when they are in the same zip arch. + */ +ZZIP_FILE* +zzip_freopen(zzip_char_t* filename, zzip_char_t* mode, ZZIP_FILE* stream) +{ + int o_flags = 0; + int o_modes = 0664; + if (!mode) mode = "rb"; + +# ifndef O_BINARY +# define O_BINARY 0 +# endif +# ifndef O_NOCTTY +# define O_NOCTTY 0 +# endif +# ifndef O_SYNC +# define O_SYNC 0 +# endif +# ifndef O_NONBLOCK +# define O_NONBLOCK 0 +# endif + + for(; *mode; mode++) + { + switch (*mode) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; /* ignore if not attached to other info */ + case 'r': o_flags |= mode[1] == '+' ? O_RDWR : O_RDONLY; break; + case 'w': o_flags |= mode[1] == '+' ? O_RDWR : O_WRONLY; + o_flags |= O_TRUNC; break; + case 'b': o_flags |= O_BINARY; break; + case 'f': o_flags |= O_NOCTTY; break; + case 'i': o_modes |= ZZIP_CASELESS; break; + case '*': o_modes |= ZZIP_NOPATHS; break; + case 'x': o_flags |= O_EXCL; break; + case 's': o_flags |= O_SYNC; break; + case 'n': o_flags |= O_NONBLOCK; break; + case 'o': o_modes &=~ 07; + o_modes |= ((mode[1] - '0'))&07; continue; + case 'g': o_modes &=~ 070; + o_modes |= ((mode[1] - '0')<<3)&070; continue; + case 'u': o_modes &=~ 0700; + o_modes |= ((mode[1] - '0')<<6)&0700; continue; + case 'q': o_modes |= ZZIP_FACTORY; break; + case 'z': /* compression level */ + continue; /* currently ignored, just for write mode */ + } + } + + { + ZZIP_FILE* fp = + zzip_open_shared_io (stream, filename, o_flags, o_modes, 0, 0); + + if (! o_modes&ZZIP_FACTORY && stream) + zzip_file_close (stream); + + return fp; + } +} + +/** + * This function will => open(2) a real/zipped file + * + * It has some magic functionality builtin - it will first try to open + * the given filename as a normal file. If it does not + * exist, the given path to the filename (if any) is split into + * its directory-part and the file-part. A ".zip" extension is + * then added to the directory-part to create the name of a + * zip-archive. That zip-archive (if it exists) is being searched + * for the file-part, and if found a zzip-handle is returned. + * + * Note that if the file is found in the normal fs-directory the + * returned structure is mostly empty and the => zzip_read call will + * use the libc => read to obtain data. Otherwise a => zzip_file_open + * is performed and any error mapped to => errno(3). + * + * There was a possibility to transfer zziplib-specific openmodes + * through o_flags but you should please not use them anymore and + * look into => zzip_open_ext_io to submit them down. This function + * is shallow in that it just extracts the zzipflags and calls
  • + * zzip_open_ext_io(filename, o_flags, zzipflags|0664, 0, 0)
+ * you must stop using this extra functionality (not well known + * anyway) since zzip_open might be later usable to open files + * for writing in which case the _EXTRAFLAGS will get in conflict. + * + * compare with => open(2) and => zzip_fopen + */ +ZZIP_FILE* +zzip_open(zzip_char_t* filename, int o_flags) +{ + /* backward compatibility */ + int o_modes = 0664; + if (o_flags & ZZIP_CASEINSENSITIVE) + { o_flags ^= ZZIP_CASEINSENSITIVE; o_modes |= ZZIP_CASELESS; } + if (o_flags & ZZIP_IGNOREPATH) + { o_flags ^= ZZIP_IGNOREPATH; o_modes |= ZZIP_NOPATHS; } + return zzip_open_ext_io(filename, o_flags, o_modes, 0, 0); +} + +/* ZZIP_ONLYZIP won't work on platforms with sizeof(int) == 16bit */ +#if ZZIP_SIZEOF_INT+0 == 2 +#undef ZZIP_ONLYZIP +#endif + +/** => zzip_open + * + * This function uses explicit ext and io instead of the internal + * defaults, setting them to zero is equivalent to => zzip_open + * + * note that the two flag types have been split into an o_flags + * (for fcntl-like openflags) and o_modes where the latter shall + * carry the zzip_flags and possibly accessmodes for unix filesystems. + * Since this version of zziplib can not write zipfiles, it is not + * yet used for anything else than zzip-specific modeflags. + */ +ZZIP_FILE* +zzip_open_ext_io(zzip_char_t* filename, int o_flags, int o_modes, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + return zzip_open_shared_io (0, filename, o_flags, o_modes, ext, io); +} + +/** => zzip_open + * + * This function takes an extra stream argument - if a handle has been + * then ext/io can be left null and the new stream handle will pick up + * the ext/io. This should be used only in specific environment however + * since => zzip_file_real does not store any ext-sequence. + * + * The benefit for this function comes in when the old file handle + * was openened from a file within a zip archive. When the new file + * is in the same zip archive then the internal zzip_dir structures + * will be shared. It is even quicker, as no check needs to be done + * anymore trying to guess the zip archive place in the filesystem, + * here we just check whether the zip archive's filepath is a prefix + * part of the filename to be opened. + * + * Note that this function is also used by => zzip_freopen that + * will unshare the old handle, thereby possibly closing the handle. + */ +ZZIP_FILE* +zzip_open_shared_io (ZZIP_FILE* stream, + zzip_char_t* filename, int o_flags, int o_modes, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + if (stream && stream->dir) + { + if (! ext) ext = stream->dir->fileext; + if (! io) io = stream->dir->io; + } + if (! io) io = zzip_get_default_io (); + + if (o_modes & (ZZIP_PREFERZIP|ZZIP_ONLYZIP)) goto try_zzip; + try_real: + /* prefer an existing real file */ + { + zzip_plugin_io_t os = (o_modes & ZZIP_ALLOWREAL) + ? zzip_get_default_io () : io; + int fd = os->fd.open(filename, o_flags); /* io->fd.open */ + if (fd != -1) + { + ZZIP_FILE* fp = calloc (1, sizeof(ZZIP_FILE)); + if (!fp) { os->fd.close(fd); return 0; } /* io->fd.close */ + + fp->fd = fd; + fp->io = os; + return fp; + } + if (o_modes & ZZIP_PREFERZIP) return 0; + } + try_zzip: + + /* if the user had it in place of a normal xopen, then + * we better defend this lib against illegal usage */ + if (o_flags & (O_CREAT|O_WRONLY)) { errno = EINVAL; return 0; } + if (o_flags & (O_RDWR)) { o_flags ^= O_RDWR; o_flags |= O_RDONLY; } + + /* this is just for backward compatibility -and strictly needed to + * prepare ourselves for more options and more options later on... */ + /*# if (o_modes & ZZIP_CASELESS) { o_flags |= ZZIP_CASEINSENSITIVE; } */ + /*# if (o_modes & ZZIP_NOPATHS) { o_flags |= ZZIP_IGNOREPATH; } */ + + /* see if we can open a file that is a zip file */ + { char basename[PATH_MAX]; + char* p; + strcpy (basename, filename); + + /* see if we can share the same zip directory */ + if (stream && stream->dir && stream->dir->realname) + { + zzip_size_t len = strlen (stream->dir->realname); + if (! memcmp (filename, stream->dir->realname, len) && + filename[len] == '/' && filename[len+1]) + { + ZZIP_FILE* fp = + zzip_file_open (stream->dir, filename+len+1, o_modes); + if (! fp) { errno = zzip_errno (stream->dir->errcode); } + return fp; + } + } + + /* per each slash in filename, check if it there is a zzip around */ + while ((p = strrchr (basename, '/'))) + { + zzip_error_t e = 0; + ZZIP_DIR* dir; + ZZIP_FILE* fp; + int fd; + + *p = '\0'; /* cut at path separator == possible zipfile basename */ + fd = __zzip_try_open (basename, o_flags|O_RDONLY|O_BINARY, ext, io); + if (fd == -1) { continue; } +/* found: */ + /* found zip-file, now try to parse it */ + dir = zzip_dir_fdopen_ext_io(fd, &e, ext, io); + if (e) { errno = zzip_errno(e); io->fd.close(fd); return 0; } + + /* (p - basename) is the lenghtof zzip_dir part of the filename */ + fp = zzip_file_open(dir, filename + (p - basename) +1, o_modes); + if (! fp) { errno = zzip_errno(dir->errcode); } + else { if (! dir->realname) dir->realname = strdup (basename); } + + zzip_dir_close(dir); + /* note: since (fp) is attached that (dir) will survive */ + /* but (dir) is implicitly closed on next zzip_close(fp) */ + + return fp; + } /*again*/ + + if (o_modes & ZZIP_PREFERZIP) goto try_real; + errno = ENOENT; return 0; + } +} + +#if defined ZZIP_LARGEFILE_RENAME && defined EOVERFLOW && defined PIC +#undef zzip_open_shared_io /* zzip_open_shared_io64 */ +#undef zzip_open_ext_io /* zzip_open_ext_io64 */ +#undef zzip_opendir_ext_io /* zzip_opendir_ext_io64 */ + +_zzip_export +ZZIP_FILE * zzip_open_shared_io(ZZIP_FILE* stream, + zzip_char_t* name, int o_flags, int o_modes, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + if (! io) return zzip_open_shared_io64 (stream, name, o_flags, o_modes, + ext, io); + errno = EOVERFLOW; return NULL; +} + +_zzip_export +ZZIP_FILE * zzip_open_ext_io(zzip_char_t* name, int o_flags, int o_modes, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + if (! io) return zzip_open_ext_io64 (name, o_flags, o_modes, ext, io); + errno = EOVERFLOW; return NULL; +} + +_zzip_export +ZZIP_DIR * zzip_opendir_ext_io(zzip_char_t* name, int o_modes, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + if (! io) return zzip_opendir_ext_io64 (name, o_modes, ext, io); + errno = EOVERFLOW; return NULL; +} + +#endif /* ZZIP_LARGEFILE_RENAME && EOVERFLOW && PIC */ + +/* ------------------------------------------------------------------- */ + +/** + * This function will rewind a real/zipped file. + * + * It seeks to the beginning of this file's data in the zip, + * or the beginning of the file for a stat'fd. + */ +int +zzip_rewind(ZZIP_FILE *fp) +{ + ZZIP_DIR *dir; + int err; + + if (! fp) + return -1; + + if (! fp->dir) + { /* stat fd */ + fp->io->fd.seeks(fp->fd,0,SEEK_SET); + return 0; + } + + dir = fp->dir; + /* + * If this is other handle than previous, save current seek pointer + */ + if (dir->currentfp != fp) + { + if (zzip_file_saveoffset(dir->currentfp) < 0) + { dir->errcode = ZZIP_DIR_SEEK; return -1; } + else + { dir->currentfp = fp; } + } + + /* seek to beginning of this file */ + if (fp->io->fd.seeks(dir->fd, fp->dataoffset, SEEK_SET) < 0) + return -1; + + /* reset the inflate init stuff */ + fp->restlen = fp->usize; + fp->offset = fp->dataoffset; + + if (fp->method) + { /* method == 8, deflate */ + memset(&fp->d_stream, 0, sizeof fp->d_stream); + err = inflateInit2(&fp->d_stream, -MAX_WBITS); + if (err != Z_OK) { goto error; } + + fp->crestlen = fp->csize; + } + + return 0; + + error: + if (fp) zzip_file_close(fp); + return err; +} + +/** + * This function will perform a => lseek(2) operation on a real/zipped file + * + * It will try to seek to the offset specified by offset, relative to whence, + * which is one of SEEK_SET, SEEK_CUR or SEEK_END. + * + * If the file-handle is wrapping a stat'able file then it will actually just + * perform a normal => lseek(2)-call. Otherwise the relative offset + * is calculated, negative offsets are transformed into positive ones + * by rewinding the file, and then data is read until the offset is + * reached. This can make the function terribly slow, but this is + * how gzio implements it, so I'm not sure there is a better way + * without using the internals of the algorithm. + */ +zzip_off_t +zzip_seek(ZZIP_FILE * fp, zzip_off_t offset, int whence) +{ + zzip_off_t cur_pos, rel_ofs, read_size, ofs; + ZZIP_DIR *dir; + + if (! fp) + return -1; + + if (! fp->dir) + { /* stat fd */ + return fp->io->fd.seeks(fp->fd, offset, whence); + } + + cur_pos = zzip_tell(fp); + + /* calculate relative offset */ + switch (whence) + { + case SEEK_SET: /* from beginning */ + rel_ofs = offset - cur_pos; + break; + case SEEK_CUR: /* from current */ + rel_ofs = offset; + break; + case SEEK_END: /* from end */ + rel_ofs = fp->usize + offset - cur_pos; + break; + default: /* something wrong */ + return -1; + } + + if (rel_ofs == 0) + return cur_pos; /* don't have to move */ + + if (rel_ofs < 0) + { /* convert backward into forward */ + if (zzip_rewind(fp) == -1) + return -1; + + read_size = cur_pos + rel_ofs; + cur_pos = 0; + }else + { /* amount to read is positive relative offset */ + read_size = rel_ofs; + } + + if (read_size < 0) /* bad offset, before beginning of file */ + return -1; + + if (read_size + cur_pos > (zzip_off_t)fp->usize) /* bad offset, past EOF */ + return -1; + + if (read_size == 0) /* nothing to read */ + return cur_pos; + + dir = fp->dir; + /* + * If this is other handle than previous, save current seek pointer + * and read the file position of `this' handle. + */ + if (dir->currentfp != fp) + { + if (zzip_file_saveoffset(dir->currentfp) < 0 + || dir->currentfp->io->fd.seeks(dir->fd, fp->offset, SEEK_SET) < 0) + { dir->errcode = ZZIP_DIR_SEEK; return -1; } + else + { dir->currentfp = fp; } + } + + if (fp->method == 0) + { /* unstore, just lseek relatively */ + ofs = fp->io->fd.tells(dir->fd); + ofs = fp->io->fd.seeks(dir->fd,read_size,SEEK_CUR); + if (ofs > 0) + { /* readjust from beginning of file */ + ofs -= fp->dataoffset; + fp->restlen = fp->usize - ofs; + } + return ofs; + }else + { /* method == 8, inflate */ + char *buf; + /*FIXME: use a static buffer! */ + buf = (char *)malloc(ZZIP_32K); + if (! buf) return -1; + + while (read_size > 0) + { + zzip_off_t size = ZZIP_32K; + if (read_size < size/*32K*/) size = read_size; + + size = zzip_file_read(fp, buf, (zzip_size_t)size); + if (size <= 0) { free(buf); return -1; } + + read_size -= size; + } + + free (buf); + } + + return zzip_tell(fp); +} + +/** + * This function will => tell(2) the current position in a real/zipped file + * + * It will return the current offset within the real/zipped file, + * measured in uncompressed bytes for the zipped-file case. + * + * If the file-handle is wrapping a stat'able file then it will actually just + * perform a normal => tell(2)-call, otherwise the offset is + * calculated from the amount of data left and the total uncompressed + * size; + */ +zzip_off_t +zzip_tell(ZZIP_FILE * fp) +{ + if (! fp) + return -1; + + if (! fp->dir) /* stat fd */ + return fp->io->fd.tells(fp->fd); + + /* current uncompressed offset is uncompressed size - data left */ + return (fp->usize - fp->restlen); +} + +/* + * Local variables: + * c-file-style: "stroustrup" + * End: + */ diff --git a/zzip/write.c b/zzip/write.c new file mode 100644 index 0000000..80905fc --- /dev/null +++ b/zzip/write.c @@ -0,0 +1,461 @@ +/* + * The write-support in zziplib is not a full-flegded interface to the + * internals that zip file-header or zip archive an contain. It's + * primary use goes for savegames or transfer `pack-n-go` archives + * where time-stamps are rather unimportant. Here we can create an + * archive with filenames and their data portions, possibly obfuscated. + * + * Author: + * Guido Draheim + * + * Copyright (c) 2003 Guido Draheim + * All rights reserved, + * use under the restrictions of the + * Lesser GNU General Public License + * or alternatively the restrictions + * of the Mozilla Public License 1.1 + */ + +#define _ZZIP_WRITE_SOURCE + +#if defined DDDD || defined DDDDD || defined DDDDDD || defined DDDDDDD +#define _ZZIP_ENABLE_WRITE +#else /* per default, we add support for passthrough to posix write */ +#define _ZZIP_POSIX_WRITE +#endif + +#include /* exported...*/ +#include + +#include +#include +#include +#include +#include + +#ifdef ZZIP_HAVE_DIRECT_H +#include +#endif + +#include +#include +#include + +#define ___ { +#define ____ } + +#ifndef EROFS +# ifdef ENOSYS +#define EROFS ENOSYS +# else +#define EROFS EPERM +#endif +#endif + +/* try real zlib routines for writing ? very experimental, very very ex... */ +#ifndef _ZZIP_ENABLE_WRITE +#define _ZZIP_TRY 0 +#else +#define _ZZIP_TRY 1 +#endif + +/* btw, is there any system that did define those different ? get away.. */ +# ifndef S_IWGRP +# define S_IWGRP 00020 +# endif +# ifndef S_IRWXO +# define S_IRWXO 00007 +# endif + +# ifdef ZZIP_HAVE_DIRECT_H +# define _mkdir(a,b) mkdir(a) +# else +# define _mkdir mkdir +# endif + +/** create a new zip archive for writing + * + * This function will create a new zip archive. The returned parameter + * is a new "zzip dir" handle that should be saved to a variable so it + * can be used a base argument for => zzip_mkdir and => zzip_creat calls. + * The returned handle represents a zip central directory that must be + * saved to disk using => zzip_closedir. + * + * Returns null on error and sets errno. Remember, according to posix + * the => creat(2) call is equivalent to + open (path, O_WRONLY | O_CREAT | O_TRUNC, o_mode) + * so any previous zip-archive will be overwritten unconditionally and + * EEXIST errors from => mkdir(2) are suppressed. (fixme: delete the + * given subtree? like suggested by O_TRUNC? not done so far!) + */ +ZZIP_DIR* +zzip_dir_creat(zzip_char_t* name, int o_mode) +{ + return zzip_dir_creat_ext_io (name, o_mode, 0, 0); +} + +/** => zzip_dir_creat + * + * If the third argument "ext" has another special meaning here, as it + * is used to ensure that a given zip-file is created with the first entry + * of the ext-list appended as an extension unless the file-path already + * ends with a file-extension registered in the list. Therefore {"",0} + * matches all files and creates them as zip-archives under the given + * nonmodified name. (Some magic here? If the path ends in the path + * separator then make a real directory even in the presence of ext-list?) + * + * This function is not yet implemented, check for #def ZZIP_NO_CREAT + * Write-support will extend => zzip_closedir with semantics to finalize the + * zip-archive by writing the zip-trailer and closing the archive file. + */ +ZZIP_DIR* +zzip_dir_creat_ext_io(zzip_char_t* name, int o_mode, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + if (! io) io = zzip_get_default_io (); + + if (io != zzip_get_default_io()) + { /* the current io-structure does not contain a "write" entry, + * and therefore this parameter is useless. Anyone to expect + * some behavior should be warned, so here we let the function + * fail bluntly - and leaving the recovery to the application + */ + errno = EINVAL; + return 0; + } + + + if (!_ZZIP_TRY) + { /* not implemented - however, we respect that a null argument to + * zzip_mkdir and zzip_creat works, so we silently still do the mkdir + */ + if (! _mkdir (name, o_mode) || errno == EEXIST) + errno = EROFS; + return 0; + } else { +# define MAX_EXT_LEN 10 + ZZIP_DIR* dir = zzip_dir_alloc (ext); + int name_len = strlen(name); + dir->realname = malloc (name_len+MAX_EXT_LEN); + if (! dir->realname) goto error; + + memcpy (dir->realname, name, name_len+1); + ___ int fd = __zzip_try_open ( + dir->realname, O_EXCL|O_TRUNC|O_WRONLY, ext, io); + if (fd != -1) { dir->fd = fd; return dir; } + + ___ zzip_strings_t* exx = ext; int exx_len; + for (; *exx ; exx++) + { + if ((exx_len = strlen (*exx)+1) <= name_len && + !memcmp (dir->realname+(name_len-exx_len), *exx, exx_len)) + break; /* keep unmodified */ + exx++; if (*exx) continue; + + if (! (exx_len = strlen (*exx)) || exx_len >= MAX_EXT_LEN) break; + memcpy (dir->realname+name_len, exx, exx_len); /* append! */ + }____; + fd = io->fd.open (dir->realname, O_CREAT|O_TRUNC|O_WRONLY, o_mode); + dir->realname[name_len] = '\0'; /* keep ummodified */ + if (fd != -1) { dir->fd = fd; return dir; } + error: + zzip_dir_free (dir); return 0; + ____; + } +} + +/** create a new archive area for writing + * + * This function will create a new archive area. This may either be a + * a new zip archive or a new directory in the filesystem. The returned + * parameter is a new "zzip dir" handle that should be saved to a variable + * so it can be used a base argument for => zzip_file_mkdir and + * => zzip_file_creat calls. The returned handle wraps both possibilities, + * it can be representing a zip central directory that must be + * saved to disk using => zzip_closedir or it is just a handle for the + * name of the real directory that still must be run through + * => zzip_closedir to release the wrapper around the directory name. + * + * The magic is pushed through the o_mode argument. Using a mode that + * has no group-write bit set (S_IWGRP = 0040) then the file is + * created as a zip directory. Note that this is unabridged of any + * umask value in the system where the argument to this function could + * be 0775 but with an umask of 0755 it turns out as 0755 for a real + * directory. Using 0755 directly would not create it as a real directory + * but as a zip archive handle. + * + * This function is not yet implemented, check for #def ZZIP_NO_CREAT + * Write-support will extend => zzip_closedir with semantics to finalize the + * zip-archive by writing the zip-trailer and closing the archive file. + * + * Returns null on error and sets errno. Remember, according to posix + * the => creat(2) call is equivalent to + open (path, O_WRONLY | O_CREAT | O_TRUNC, o_mode) + * so any previous zip-archive will be overwritten unconditionally and + * EEXIST errors from => mkdir(2) are suppressed. (fixme: delete the + * given subtree? like suggested by O_TRUNC? not done so far!) + */ +ZZIP_DIR* +zzip_createdir(zzip_char_t* name, int o_mode) +{ + if (o_mode & S_IWGRP) + { + if (-1 == _mkdir(name, o_mode) && errno != EEXIST) /* fail */ + return 0; + return zzip_opendir (name); + } else + return zzip_dir_creat (name, o_mode); +} + +/** => zzip_file_creat => mkdir(2), zzip_dir_creat + * + * This function has an additional primary argument over the posix + * mkdir(2) - if it is null then this function behaves just like + * posix mkdir(2). The zzip_dir argument can be set to the result + * of a => zzip_createdir which allows for some magic that the + * given directory name is created as an entry in the zip archive. + * + * If the given dir name argument is not within the basepath of + * the zip central directory then a real directory is created. + * Any EEXIST errors are not suppressed unlike with => zzip_createdir + * + * Standard usage accepts a global/threaded/modular ZZIP_DIR pointer + * for all zip archive operations like in: + ZZIP_DIR* zip = zzip_createdir (sysconfpath, 0755, zip); + zzip_file_mkdir (zip, filepath[i], 0755); + ZZIP_FILE* file = zzip_file_creat (zip, filename[i], 0644); + zzip_write (file, buf, len); + zzip_close (file); file = 0; + zzip_closedir (zip); zip = 0; + * + * compare with => zzip_mkdir inline macro which allows to + * collapse the examples script to + #define zzip_savefile myproject_saveconfig + #include + ZZIP_DIR* zzip_savefile = zzip_createdir (sysconfpath, 0755); + zzip_mkdir (filepath[i], 0755); + ZZIP_FILE* file = zzip_creat(filepath[i], 0644); + zzip_write (file, buf, len); + zzip_close (file); file = 0; + zzip_closedir (zip_savefile); + */ +int +zzip_file_mkdir(ZZIP_DIR* dir, zzip_char_t* name, int o_mode) +{ + if (! dir) + return _mkdir(name, o_mode); + + if (!_ZZIP_TRY) + {/* not implemented */ + errno = EROFS; + return -1; + } else { + errno = EROFS; + return -1; + } +} + +/** start next file entry in a zip archive => creat(2) + * + * This function will create a new file within a zzip archive, the + * one given as the primary argument and additionally to the posix + * creat(2) - just like zzip_mkdir has an additional argument over + * the posix mkdir(2) spec. For this function the primary parameter + * can be null as well thereby creating a real file instead of a new + * one inside the zip-archive otherwise given. If the primary parameter is + * not null but wraps a real directory then all new files are also real. + * + * This function is not yet implemented, check for #def ZZIP_NO_CREAT + * + * Returns NULL on an error setting errno, and opening a file _within_ + * a zip archive using O_RDONLY (and similar stuff) will surely lead to + * an error. + */ +ZZIP_FILE* +zzip_file_creat(ZZIP_DIR* dir, zzip_char_t* name, int o_mode) +{ + if (! dir) + return zzip_open (name, o_mode); + + if (!_ZZIP_TRY) + {/* not implemented */ + errno = EROFS; + return 0; + } else { + errno = EROFS; + return 0; + } +} + +/** write to zzip storage also: write(2), zlib(3) + * + * This function will write data to a file descriptor. If the file + * descriptor represents a real file then it will be forwarded to + * call posix => write(2) directly. If it is a descriptor for a + * file within a zip directory then the data will be "deflated" + * using => zlib(3) and appended to the zip archive file. + */ +zzip_ssize_t +zzip_write(ZZIP_FILE* file, const void* ptr, zzip_size_t len) +{ + if (zzip_file_real(file)) + return write (zzip_realfd (file), ptr, len); + else + return zzip_file_write (file, ptr, len); +} + +/** => zzip_write also: zzip_file_creat + * + * This function will write data to a file descriptor inside a zip + * archive. The data will be "deflated" using => zlib(3) compression + * and appended to the end of the zip archive file. Only one file + * descriptor may be open per zzip_dir archive handle (fifo-like). + * + * This function is not yet implemented, check for #def ZZIP_NO_CREAT + * It returns immediately -1 and sets errno=EROFS for indication. + */ +zzip_ssize_t +zzip_file_write(ZZIP_FILE* file, const void* ptr, zzip_size_t len) +{ + if (!_ZZIP_TRY) + {/* not implemented */ + errno = EROFS; + return -1; + } else { + /* add calls to zlib here... */ + errno = EROFS; + return -1; + } +} + +/** => zzip_write + * This function is the stdc variant for writing and the arguments + * are forwarded to => zzip_write - the return value is floored to + * null as for STDC spec but there is no zzip_ferror call so far + * for the zziplib (later? is it actually needed?). + * + * This function is not yet implemented, check for #def ZZIP_NO_CREAT + * Write-support extends => zzip_close with semantics to write out a + * file-trailer to the zip-archive leaving a name/offset marker in + * the (still-open) ZZIP_DIR handle. + */ +zzip_size_t +zzip_fwrite(const void* ptr, zzip_size_t len, zzip_size_t multiply, + ZZIP_FILE* file) +{ + zzip_ssize_t value = zzip_write (file, ptr, len * multiply); + if (value == -1) + value = 0; + return (zzip_size_t) value; +} + +#if 0 /* pure documentation */ +/** create a zipped file/directory also: zzip_dir_creat, mkdir(2) + * + * This function creates a directory entry in the default zip-archive. + * If you did not specify a "#define zzip_savefile somevar" + * then the default zip-archive is null and all directories are + * created as real directories in the filesystem. This function is + * really a preprocessor macro or preferably an inline function + * around => zzip_file_mkdir, there is no such symbol generated + * into the library. The prototype is modelled after the posix + * => mkdir(2) call. + #ifndef zzip_savefile + #define zzip_savefile 0 + #endif + #define zzip_mkdir(name,mode) \ - + zzip_file_mkdir(zzip_savefile,name,mode) + * + */ +int +zzip_mkdir(zzip_char_t* name, int o_mode) inline +{ + return zzip_file_creat(zzip_savefile, name, mode); +} +#endif + +#if 0 /* pure documentation */ +/** => zzip_mkdir also: creat(2), zzip_start + * + * This function creates a file in the default zip-archive. + * If you did not specify a "#define zzip_savefile somevar" + * then the default zip-archive is null and all files are created + * as real files. This function is really a preprocessor macro + * or preferably an inline function around => zzip_file_creat, + * there is no such symbol generated into the library. The prototype + * is modelled after the posix => creat(2) call. + #ifndef zzip_savefile + #define zzip_savefile 0 + #endif + #define zzip_creat(name,mode) \ - + zzip_file_creat(zzip_savefile,name,mode) + */ +ZZIP_FILE* +zzip_creat(zzip_char_t* name, int o_mode) inline +{ + return zzip_file_creat(zzip_savefile, name, mode); +} +#endif + + +#if 0 /* pure documentation */ +/** start writing to the magic zzip_savefile also: zzip_creat, zzip_write + * + * open a zip archive for writing via the magic zzip_savefile macro + * variable. The name and mode are given to => zzip_createdir and + * the result is stored into => zzip_savefile - if the => zzip_savefile + * did already have a zzip_dir handle then it is automatically + * finalized with => zzip_sync and the handle closed and the + * zzip_savefile variable reused for the new zip archive just started + * with this call. - This function is really a preprocessor macro + * or preferably an inline function around => zzip_dir_create, there + * is no such symbol generated into the library. + #ifndef zzip_savefile + #define zzip_savefile 0 + #endif + #define zzip_start(name,mode,ext) \ - + { if (zzip_savefile) zzip_closedir(zzip_savefile); \ - + zzip_savefile = zzip_createdir(name,mode,ext); } + * This function returns null on error or a zzip_dir handle on + * success. It is perfectly okay to continue with a null in the + * zzip_savefile variable since it makes subsequent calls to + * => zzip_creat and => zzip_mkdir to run as => creat(2) / => mkdir(2) + * on the real filesystem. + */ +void +zzip_mkfifo(zzip_char_t* name, int o_mode) inline +{ + if (zzip_savefile) zzip_closedir (zzip_savefile); + zzip_savefile = zzip_createdir(name, o_mode); +} +#endif + +#if 0 /* pure documentation */ +/** => zzip_mkfifo also: zzip_closedir, sync(2) + * + * finalize a zip archive thereby writing the central directory to + * the end of the file. If it was a real directory then we do just + * nothing - even that the prototype of the call itself is modelled + * to be similar to the posix => sync(2) call. This function is + * really a preprocessor macro or preferably an inline function + * around => zzip_closedir, there is no such symbol generated + * into the library. + #ifndef zzip_savefile + #define zzip_savefile 0 + #endif + #define zzip_sync(name,mode) \ - + { zzip_closedir(zzip_savefile); zzip_savefile = 0; } + * + */ +void +zzip_sync(void) inline +{ + zzip_closedir (zzip_savefile); zzip_savefile = 0; +} +#endif + +/* + * Local variables: + * c-file-style: "stroustrup" + * End: + */ diff --git a/zzip/zip.c b/zzip/zip.c new file mode 100644 index 0000000..f3fa88d --- /dev/null +++ b/zzip/zip.c @@ -0,0 +1,713 @@ +/* + * Author: + * Guido Draheim + * Tomi Ollila + * + * Copyright (c) 1999,2000,2001,2002,2003 Guido Draheim + * All rights reserved, + * use under the restrictions of the + * Lesser GNU General Public License + * or alternatively the restrictions + * of the Mozilla Public License 1.1 + */ + +#include /* archive handling */ +#include +#include + +#include +#include +#include +#include +#include +#ifdef ZZIP_HAVE_SYS_STAT_H +#include +#endif + +#include +#include + +#define __sizeof(X) ((zzip_ssize_t)(sizeof(X))) + +/* per default, we use a little hack to correct bad z_rootseek parts */ +#define ZZIP_CORRECT_ROOTSEEK 1 + +/* ------------------------- fetch helpers --------------------------------- */ + +/** + * Make 32 bit value in host byteorder from little-endian mapped octet-data + * (works also on machines which SIGBUS on misaligned data access (eg. 68000)) + */ +uint32_t __zzip_get32(unsigned char * s) +{ + return ((uint32_t)s[3] << 24) | ((uint32_t)s[2] << 16) + | ((uint32_t)s[1] << 8) | (uint32_t)s[0]; +} + +/** => __zzip_get32 + * This function does the same for a 16 bit value. + */ +uint16_t __zzip_get16(unsigned char * s) +{ + return ((uint16_t)s[1] << 8) | (uint16_t)s[0]; +} + +/* --------------------------- internals -------------------------------- */ +/* internal functions of zziplib, avoid at all cost, changes w/o warning. + * we do export them for debugging purpose and special external tools + * which know what they do and which can adapt from version to version + */ + +int __zzip_find_disk_trailer( int fd, zzip_off_t filesize, + struct zzip_disk_trailer * trailer, + zzip_plugin_io_t io); +int __zzip_parse_root_directory( int fd, + struct zzip_disk_trailer * trailer, + struct zzip_dir_hdr ** hdr_return, + zzip_plugin_io_t io); + +_zzip_inline char* __zzip_aligned4(char* p); + +/* ------------------------ harden routines ------------------------------ */ + +#ifdef ZZIP_HARDEN +/* + * check for inconsistent values in trailer and prefer lower seek value + * - we fix values assuming the root directory was written at the end + * and it is just before the zip trailer. Therefore, ... + */ +_zzip_inline static void __fixup_rootseek( + zzip_off_t offset_of_trailer, + struct zzip_disk_trailer* trailer) +{ + if ( (zzip_off_t) ZZIP_GET32(trailer->z_rootseek) > + offset_of_trailer - (zzip_off_t) ZZIP_GET32(trailer->z_rootsize) && + offset_of_trailer > (zzip_off_t) ZZIP_GET32(trailer->z_rootsize)) + { + register zzip_off_t offset; + offset = offset_of_trailer - ZZIP_GET32(trailer->z_rootsize); + trailer->z_rootseek[0] = offset & 0xff; + trailer->z_rootseek[1] = offset >> 8 & 0xff; + trailer->z_rootseek[2] = offset >> 16 & 0xff; + trailer->z_rootseek[3] = offset >> 24 & 0xff; + HINT2("new rootseek=%li", + (long) ZZIP_GET32(trailer->z_rootseek)); + } +} +#define __correct_rootseek(A,B,C) + +#elif defined ZZIP_CORRECT_ROOTSEEK +/* store the seekvalue of the trailer into the "z_magic" field and with + * a 64bit off_t we overwrite z_disk/z_finaldisk as well. If you change + * anything in zziplib or dump the trailer structure then watch out that + * these are still unused, so that this code may still (ab)use those. */ +#define __fixup_rootseek(_offset_of_trailer, _trailer) \ + *(zzip_off_t*)_trailer = _offset_of_trailer; +#define __correct_rootseek( _u_rootseek, _u_rootsize, _trailer) \ + if (_u_rootseek > *(zzip_off_t*)_trailer - _u_rootsize) \ + _u_rootseek = *(zzip_off_t*)_trailer - _u_rootsize; +#else +#define __fixup_rootseek(A,B) +#define __correct_rootseek(A,B,C) +#endif + + +#ifdef DEBUG +_zzip_inline static void __debug_dir_hdr (struct zzip_dir_hdr* hdr) +{ + if (sizeof(struct zzip_dir_hdr) > sizeof(struct zzip_root_dirent)) + { WARN1("internal sizeof-mismatch may break wreakage"); } + /* the internal directory structure is never bigger than the + * external zip central directory space had been beforehand + * (as long as the following assertion holds...) + */ + + if (((unsigned)hdr)&3) + { NOTE1("this machine's malloc(3) returns sth. not u32-aligned"); } + /* we assume that if this machine's malloc has returned a non-aligned + * memory block, then it is actually safe to access misaligned data, and + * since it does only affect the first hdr it should not even bring about + * too much of that cpu's speed penalty + */ +} +#else +#define __debug_dir_hdr(X) +#endif + +/* -------------------------- low-level interface -------------------------- */ + +#if defined BUFSIZ +#if BUFSIZ == 1024 || BUFSIZ == 512 || BUFSIZ == 256 +#define ZZIP_BUFSIZ BUFSIZ +#endif +#endif + +#ifndef ZZIP_BUFSIZ +#define ZZIP_BUFSIZ 512 +/* #define ZZIP_BUFSIZ 64 */ /* for testing */ +#endif + +/** + * This function is used by => zzip_file_open. It tries to find + * the zip's central directory info that is usually a few + * bytes off the end of the file. + */ +int +__zzip_find_disk_trailer(int fd, zzip_off_t filesize, + struct zzip_disk_trailer * trailer, + zzip_plugin_io_t io) +{ +#ifdef DEBUG +#define return(val) { e=val; HINT2("%s", zzip_strerror(e)); goto cleanup; } +#else +#define return(val) { e=val; goto cleanup; } +#endif + register int e; + +#ifndef _LOWSTK + auto char buffer[2*ZZIP_BUFSIZ]; + char* buf = buffer; +#else + char* buf = malloc(2*ZZIP_BUFSIZ); +#endif + zzip_off_t offset = 0; + zzip_ssize_t maplen = 0; /* mmap(),read(),getpagesize() use size_t !! */ + char* fd_map = 0; + + if (!trailer) + { return(EINVAL); } + + if (filesize < __sizeof(struct zzip_disk_trailer)) + { return(ZZIP_DIR_TOO_SHORT); } + + if (!buf) + { return(ZZIP_OUTOFMEM); } + + offset = filesize; /* a.k.a. old offset */ + while(1) /* outer loop */ + { + register unsigned char* mapped; + + if (offset <= 0) { return(ZZIP_DIR_EDH_MISSING); } + + /* trailer cannot be farther away than 64K from fileend */ + if (filesize-offset > 64*1024) + { return(ZZIP_DIR_EDH_MISSING); } + + /* the new offset shall overlap with the area after the old offset! */ + if (USE_MMAP && io->fd.sys) + { + zzip_off_t mapoff = offset; + { + zzip_ssize_t pagesize = _zzip_getpagesize (io->fd.sys); + if (pagesize < ZZIP_BUFSIZ) goto non_mmap; /* an error? */ + if (mapoff == filesize && filesize > pagesize) + mapoff -= pagesize; + if (mapoff < pagesize) { + maplen = (zzip_ssize_t)mapoff + pagesize; mapoff = 0; + } else { + mapoff -= pagesize; maplen = 2*pagesize; + if ((zzip_ssize_t)mapoff & (pagesize-1)) { /*only 1. run */ + pagesize -= (zzip_ssize_t)mapoff & (pagesize-1); + mapoff += pagesize; + maplen -= pagesize; + } + } + if (mapoff + maplen > filesize) maplen = filesize - mapoff; + } + + fd_map = _zzip_mmap(io->fd.sys, fd, mapoff, (zzip_size_t)maplen); + if (fd_map == MAP_FAILED) goto non_mmap; + mapped = fd_map; offset = mapoff; /* success */ + HINT3("mapped *%p len=%li", fd_map, (long) maplen); + } else { + non_mmap: + fd_map = 0; /* have no mmap */ + { + zzip_off_t pagesize = ZZIP_BUFSIZ; + if (offset == filesize && filesize > pagesize) + offset -= pagesize; + if (offset < pagesize) { + maplen = (zzip_ssize_t)offset + pagesize; offset = 0; + } else { + offset -= pagesize; maplen = 2*pagesize; + if ((zzip_ssize_t)offset & (pagesize-1)) { /*on 1st run*/ + pagesize -= (zzip_ssize_t)offset & (pagesize-1); + offset += pagesize; + maplen -= pagesize; + } + } + if (offset + maplen > filesize) maplen = filesize - offset; + } + + if (io->fd.seeks(fd, offset, SEEK_SET) < 0) + { return(ZZIP_DIR_SEEK); } + if (io->fd.read(fd, buf, (zzip_size_t)maplen) < maplen) + { return(ZZIP_DIR_READ); } + mapped = buf; /* success */ + HINT5("offs=$%lx len=%li filesize=%li pagesize=%i", + (long)offset, (long)maplen, (long)filesize, ZZIP_BUFSIZ); + } + + {/* now, check for the trailer-magic, hopefully near the end of file */ + register unsigned char* end = mapped + maplen; + register unsigned char* tail; + for (tail = end-1; (tail >= mapped); tail--) + { + if ((*tail == 'P') && /* quick pre-check for trailer magic */ + end-tail >= __sizeof(*trailer)-2 && + ZZIP_DISK_TRAILER_CHECKMAGIC(tail)) + { + /* if the file-comment is not present, it happens + that the z_comment field often isn't either */ + if (end-tail >= __sizeof(*trailer)) + { + memcpy (trailer, tail, sizeof(*trailer)); + }else{ + memcpy (trailer, tail, sizeof(*trailer)-2); + trailer->z_comment[0] = 0; + trailer->z_comment[1] = 0; + } + + __fixup_rootseek (offset + tail-mapped, trailer); + { return(0); } + } + } + } + + if (USE_MMAP && fd_map) + { + HINT3("unmap *%p len=%li", fd_map, (long) maplen); + _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t)maplen); + fd_map = 0; + } + } /*outer loop*/ + + cleanup: + if (USE_MMAP && fd_map) + { + HINT3("unmap *%p len=%li", fd_map, (long) maplen); + _zzip_munmap(io->fd.sys, fd_map, (zzip_size_t)maplen); + } +# ifdef _LOWSTK + free(buf); +# endif +# undef return + return e; +} + +/* + * making pointer alignments to values that can be handled as structures + * is tricky. We assume here that an align(4) is sufficient even for + * 64 bit machines. Note that binary operations are not usually allowed + * to pointer types but we do need only the lower bits in this implementation, + * so we can just cast the value to a long value. + */ +_zzip_inline char* __zzip_aligned4(char* p) +{ +#define aligned4 __zzip_aligned4 + p += ((long)p)&1; /* warnings about truncation of a "pointer" */ + p += ((long)p)&2; /* to a "long int" may be safely ignored :) */ + return p; +} + +/** + * This function is used by => zzip_file_open, it is usually called after + * => __zzip_find_disk_trailer. It will parse the zip's central directory + * information and create a zziplib private directory table in + * memory. + */ +int +__zzip_parse_root_directory(int fd, + struct zzip_disk_trailer * trailer, + struct zzip_dir_hdr ** hdr_return, + zzip_plugin_io_t io) +{ + auto struct zzip_root_dirent dirent; + struct zzip_dir_hdr * hdr; + struct zzip_dir_hdr * hdr0; + uint16_t * p_reclen = 0; + short entries; + long offset; /* offset from start of root directory */ + char* fd_map = 0; + int32_t fd_gap = 0; + uint16_t u_entries = ZZIP_GET16(trailer->z_entries); + uint32_t u_rootsize = ZZIP_GET32(trailer->z_rootsize); + uint32_t u_rootseek = ZZIP_GET32(trailer->z_rootseek); + __correct_rootseek (u_rootseek, u_rootsize, trailer); + + hdr0 = (struct zzip_dir_hdr*) malloc(u_rootsize); + if (!hdr0) + return ZZIP_DIRSIZE; + hdr = hdr0; __debug_dir_hdr (hdr); + + if (USE_MMAP && io->fd.sys) + { + fd_gap = u_rootseek & (_zzip_getpagesize(io->fd.sys)-1) ; + HINT4(" mapseek=0x%x, maplen=%d, fd_gap=%d", + u_rootseek-fd_gap, u_rootsize+fd_gap, fd_gap); + fd_map = _zzip_mmap(io->fd.sys, fd, + u_rootseek-fd_gap, u_rootsize+fd_gap); + /* if mmap failed we will fallback to seek/read mode */ + if (fd_map == MAP_FAILED) { + NOTE2("map failed: %s",strerror(errno)); + fd_map=0; + }else{ + HINT3("mapped *%p len=%i", fd_map, u_rootsize+fd_gap); + } + } + + for (entries=u_entries, offset=0; entries > 0; entries--) + { + register struct zzip_root_dirent * d; + uint16_t u_extras, u_comment, u_namlen; + + if (fd_map) + { d = (void*)(fd_map+fd_gap+offset); } /* fd_map+fd_gap==u_rootseek */ + else + { + if (io->fd.seeks(fd, u_rootseek+offset, SEEK_SET) < 0) + return ZZIP_DIR_SEEK; + if (io->fd.read(fd, &dirent, sizeof(dirent)) < __sizeof(dirent)) + return ZZIP_DIR_READ; + d = &dirent; + } + + if (offset+sizeof(*d) > u_rootsize) + { FAIL2("%i's entry stretches beyond root directory", entries); break;} + +# if 0 && defined DEBUG + zzip_debug_xbuf ((unsigned char*) d, sizeof(*d) + 8); +# endif + + u_extras = ZZIP_GET16(d->z_extras); + u_comment = ZZIP_GET16(d->z_comment); + u_namlen = ZZIP_GET16(d->z_namlen); + HINT5("offset=0x%lx, size %ld, dirent *%p, hdr %p\n", + offset+u_rootseek, (long)u_rootsize, d, hdr); + + /* writes over the read buffer, Since the structure where data is + copied is smaller than the data in buffer this can be done. + It is important that the order of setting the fields is considered + when filling the structure, so that some data is not trashed in + first structure read. + at the end the whole copied list of structures is copied into + newly allocated buffer */ + hdr->d_crc32 = ZZIP_GET32(d->z_crc32); + hdr->d_csize = ZZIP_GET32(d->z_csize); + hdr->d_usize = ZZIP_GET32(d->z_usize); + hdr->d_off = ZZIP_GET32(d->z_off); + hdr->d_compr = (uint8_t)ZZIP_GET16(d->z_compr); + if (hdr->d_compr > 255) hdr->d_compr = 255; + + if (offset+sizeof(*d) + u_namlen > u_rootsize) + { FAIL2("%i's name stretches beyond root directory", entries); break;} + + if (fd_map) + { memcpy(hdr->d_name, fd_map+fd_gap+offset+sizeof(*d), u_namlen); } + else { io->fd.read(fd, hdr->d_name, u_namlen); } + hdr->d_name[u_namlen] = '\0'; + hdr->d_namlen = u_namlen; + + /* update offset by the total length of this entry -> next entry */ + offset += sizeof(*d) + u_namlen + u_extras + u_comment; + + if (offset > (long)u_rootsize) + { FAIL2("%i's end beyond root directory", entries); entries--; break;} + + HINT5("file %d { compr=%d crc32=$%x offset=%d", + entries, hdr->d_compr, hdr->d_crc32, hdr->d_off); + HINT5("csize=%d usize=%d namlen=%d extras=%d", + hdr->d_csize, hdr->d_usize, u_namlen, u_extras); + HINT5("comment=%d name='%s' %s } ", + u_comment, hdr->d_name, "",(int) sizeof(*d)); + + p_reclen = &hdr->d_reclen; + + { register char* p = (char*) hdr; + register char* q = aligned4 (p + sizeof(*hdr) + u_namlen + 1); + *p_reclen = (uint16_t)(q - p); + hdr = (struct zzip_dir_hdr*) q; + } + }/*for*/ + + if (USE_MMAP && fd_map) + { + HINT3("unmap *%p len=%i", fd_map, u_rootsize+fd_gap); + _zzip_munmap(io->fd.sys, fd_map, u_rootsize+fd_gap); + } + + if (p_reclen) + { + *p_reclen = 0; /* mark end of list */ + + if (hdr_return) + *hdr_return = hdr0; + } /* else zero (sane) entries */ + return (entries ? ZZIP_CORRUPTED : 0); +} + +/* ------------------------- high-level interface ------------------------- */ + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +static zzip_strings_t* zzip_get_default_ext(void) +{ + static zzip_strings_t ext [] = + { + ".zip", ".ZIP", /* common extension */ +# ifdef ZZIP_USE_ZIPLIKES + ".pk3", ".PK3", /* ID Software's Quake3 zipfiles */ + ".jar", ".JAR", /* Java zipfiles */ +# endif + 0 + }; + + return ext; +} + +/** + * allocate a new ZZIP_DIR handle and do basic + * initializations before usage by => zzip_dir_fdopen + * => zzip_dir_open => zzip_file_open or through + * => zzip_open + * (ext==null flags uses { ".zip" , ".ZIP" } ) + * (io ==null flags use of posix io defaults) + */ +ZZIP_DIR* +zzip_dir_alloc_ext_io (zzip_strings_t* ext, const zzip_plugin_io_t io) +{ + ZZIP_DIR* dir; + if ((dir = (ZZIP_DIR *)calloc(1, sizeof(*dir))) == NULL) + return 0; + + /* dir->fileext is currently unused - so what, still initialize it */ + dir->fileext = ext ? ext : zzip_get_default_ext(); + dir->io = io ? io : zzip_get_default_io (); + return dir; +} + +/** => zzip_dir_alloc_ext_io + * this function is obsolete - it was generally used for implementation + * and exported to let other code build on it. It is now advised to + * use => zzip_dir_alloc_ext_io now on explicitly, just set that second + * argument to zero to achieve the same functionality as the old style. + */ +ZZIP_DIR* +zzip_dir_alloc (zzip_strings_t* fileext) +{ + return zzip_dir_alloc_ext_io (fileext, 0); +} + +/** + * will free the zzip_dir handle unless there are still + * zzip_files attached (that may use its cache buffer). + * This is the inverse of => zzip_dir_alloc , and both + * are helper functions used implicitly in other zzipcalls + * e.g. => zzip_dir_close = zzip_close + * + * returns zero on sucess + * returns the refcount when files are attached. + */ +int +zzip_dir_free(ZZIP_DIR * dir) +{ + if (dir->refcount) + return (dir->refcount); /* still open files attached */ + + if (dir->fd >= 0) dir->io->fd.close(dir->fd); + if (dir->hdr0) free(dir->hdr0); + if (dir->cache.fp) free(dir->cache.fp); + if (dir->cache.buf32k) free(dir->cache.buf32k); + if (dir->realname) free(dir->realname); + free(dir); + return 0; +} + +/** + * It will also => free(2) the => ZZIP_DIR-handle given. + * the counterpart for => zzip_dir_open + * see also => zzip_dir_free + */ +int +zzip_dir_close(ZZIP_DIR * dir) +{ + dir->refcount &=~ 0x10000000; /* explicit dir close */ + return zzip_dir_free(dir); +} + +/** + * used by the => zzip_dir_open and zzip_opendir(2) call. Opens the + * zip-archive as specified with the fd which points to an + * already openend file. This function then search and parse + * the zip's central directory. + *

+ * NOTE: refcount is zero, so an _open/_close pair will also delete + * this _dirhandle + */ +ZZIP_DIR * +zzip_dir_fdopen(int fd, zzip_error_t * errcode_p) +{ + return zzip_dir_fdopen_ext_io(fd, errcode_p, 0, 0); +} + +static zzip_error_t __zzip_dir_parse (ZZIP_DIR* dir); /* forward */ + +/** => zzip_dir_fdopen + * this function uses explicit ext and io instead of the internal + * defaults, setting these to zero is equivalent to => zzip_dir_fdopen + */ +ZZIP_DIR * +zzip_dir_fdopen_ext_io(int fd, zzip_error_t * errcode_p, + zzip_strings_t* ext, const zzip_plugin_io_t io) +{ + zzip_error_t rv; + ZZIP_DIR * dir; + + if ((dir = zzip_dir_alloc_ext_io (ext, io)) == NULL) + { rv = ZZIP_OUTOFMEM; goto error; } + + dir->fd = fd; + if ((rv = __zzip_dir_parse (dir))) + goto error; + + dir->hdr = dir->hdr0; + dir->refcount |= 0x10000000; + + if (errcode_p) *errcode_p = rv; + return dir; +error: + if (dir) zzip_dir_free(dir); + if (errcode_p) *errcode_p = rv; + return NULL; +} + +static zzip_error_t +__zzip_dir_parse (ZZIP_DIR* dir) +{ + zzip_error_t rv; + zzip_off_t filesize; + struct zzip_disk_trailer trailer; + /* if (! dir || dir->fd < 0) + * { rv = EINVAL; goto error; } + */ + + HINT2("------------------ fd=%i", (int) dir->fd); + if ((filesize = dir->io->fd.filesize(dir->fd)) < 0) + { rv = ZZIP_DIR_STAT; goto error; } + + HINT2("------------------ filesize=%ld", (long) filesize); + if ((rv = __zzip_find_disk_trailer(dir->fd, filesize, &trailer, + dir->io)) != 0) + { goto error; } + + HINT5("directory = { entries= %d/%d, size= %d, seek= %d } ", + ZZIP_GET16(trailer.z_entries), ZZIP_GET16(trailer.z_finalentries), + ZZIP_GET32(trailer.z_rootsize), ZZIP_GET32(trailer.z_rootseek)); + + if ( (rv = __zzip_parse_root_directory(dir->fd, &trailer, &dir->hdr0, + dir->io)) != 0) + { goto error; } + error: + return rv; +} + +/** + * will attach a .zip extension and tries to open it + * the with => open(2). This is a helper function for + * => zzip_dir_open, => zzip_opendir and => zzip_open. + */ +int +__zzip_try_open(zzip_char_t* filename, int filemode, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + auto char file[PATH_MAX]; + int fd; + zzip_size_t len = strlen (filename); + + if (len+4 >= PATH_MAX) return -1; + memcpy(file, filename, len+1); + + if (!io) io = zzip_get_default_io(); + if (!ext) ext = zzip_get_default_ext(); + + for ( ; *ext ; ++ext) + { + strcpy (file+len, *ext); + fd = io->fd.open(file, filemode); + if (fd != -1) return fd; + } + return -1; +} + +/** + * Opens the zip-archive (if available). + * the two ext_io arguments will default to use posix io and + * a set of default fileext that can atleast add .zip ext itself. + */ +ZZIP_DIR* +zzip_dir_open(zzip_char_t* filename, zzip_error_t* e) +{ + return zzip_dir_open_ext_io (filename, e, 0, 0); +} + +/** => zzip_dir_open + * this function uses explicit ext and io instead of the internal + * defaults. Setting these to zero is equivalent to => zzip_dir_open + */ +ZZIP_DIR* +zzip_dir_open_ext_io(zzip_char_t* filename, zzip_error_t* e, + zzip_strings_t* ext, zzip_plugin_io_t io) +{ + int fd; + + if (!io) io = zzip_get_default_io(); + if (!ext) ext = zzip_get_default_ext(); + + fd = io->fd.open(filename, O_RDONLY|O_BINARY); + if (fd != -1) + { return zzip_dir_fdopen_ext_io(fd, e, ext, io); } + else + { + fd = __zzip_try_open(filename, O_RDONLY|O_BINARY, ext, io); + if (fd != -1) + { return zzip_dir_fdopen_ext_io(fd, e, ext, io); } + else + { + if (e) { *e = ZZIP_DIR_OPEN; } + return 0; + } + } +} + +/** + * fills the dirent-argument with the values and + * increments the read-pointer of the dir-argument. + *

+ * returns 0 if there no entry (anymore). + */ +int +zzip_dir_read(ZZIP_DIR * dir, ZZIP_DIRENT * d ) +{ + if (! dir || ! dir->hdr || ! d) return 0; + + d->d_compr = dir->hdr->d_compr; + d->d_csize = dir->hdr->d_csize; + d->st_size = dir->hdr->d_usize; + d->d_name = dir->hdr->d_name; + + if (! dir->hdr->d_reclen) + { dir->hdr = 0; } + else + { dir->hdr = (struct zzip_dir_hdr *)((char *)dir->hdr + dir->hdr->d_reclen); } + + return 1; +} + +/* + * Local variables: + * c-file-style: "stroustrup" + * End: + */ -- 2.40.0