diff --git a/archivers/zip.c b/archivers/zip.c index 7e96e07..01dc126 100644 --- a/archivers/zip.c +++ b/archivers/zip.c @@ -26,6 +26,12 @@ #define __PHYSICSFS_INTERNAL__ #include "physfs_internal.h" +/* + * When sorting the zip entries in an archive, we use a modified QuickSort. + * When there are less then ZIP_QUICKSORT_THRESHOLD entries left to sort, + * we switch over to an InsertionSort for the remainder. Tweak to taste. + */ +#define ZIP_QUICKSORT_THRESHOLD 4 /* * A buffer of ZIP_READBUFSIZE is malloc() for each compressed file opened, @@ -41,23 +47,42 @@ */ #define ZIP_READBUFSIZE (16 * 1024) + +/* + * Entries are "unresolved" until they are first opened. At that time, + * local file headers parsed/validated, data offsets will be updated to look + * at the actual file data instead of the header, and symlinks will be + * followed and optimized. This means that we don't seek and read around the + * archive until forced to do so, and after the first time, we had to do + * less reading and parsing, which is very CD-ROM friendly. + */ +typedef enum +{ + ZIP_UNRESOLVED_FILE, + ZIP_UNRESOLVED_SYMLINK, + ZIP_RESOLVING, + ZIP_RESOLVED, + ZIP_BROKEN_FILE, + ZIP_BROKEN_SYMLINK, +} ZipResolveType; + + /* * One ZIPentry is kept for each file in an open ZIP archive. */ -typedef struct +typedef struct _ZIPentry { - char *name; /* Name of file in archive */ - char *symlink; /* NULL or file we link to */ - int fixed_up; /* Have we updated offset? */ - PHYSFS_uint32 offset; /* offset of data in file */ - PHYSFS_uint16 version; /* version made by */ - PHYSFS_uint16 version_needed; /* version needed to extract */ - PHYSFS_uint16 flag; /* general purpose bit flag */ - PHYSFS_uint16 compression_method; /* compression method */ - PHYSFS_uint32 crc; /* crc-32 */ - PHYSFS_uint32 compressed_size; /* compressed size */ - PHYSFS_uint32 uncompressed_size; /* uncompressed size */ - PHYSFS_sint64 last_mod_time; /* last file mod time */ + char *name; /* Name of file in archive */ + struct _ZIPentry *symlink; /* NULL or file we symlink to */ + ZipResolveType resolved; /* Have we resolved file/symlink? */ + PHYSFS_uint32 offset; /* offset of data in archive */ + PHYSFS_uint16 version; /* version made by */ + PHYSFS_uint16 version_needed; /* version needed to extract */ + PHYSFS_uint16 compression_method; /* compression method */ + PHYSFS_uint32 crc; /* crc-32 */ + PHYSFS_uint32 compressed_size; /* compressed size */ + PHYSFS_uint32 uncompressed_size; /* uncompressed size */ + PHYSFS_sint64 last_mod_time; /* last file mod time */ } ZIPentry; /* @@ -98,13 +123,6 @@ typedef struct #define UNIX_FILETYPE_SYMLINK 0120000 -#define MAXZIPENTRYSIZE 256 /* !!! FIXME: get rid of this. */ - - -/* Number of symlinks to follow before we assume it's a recursive link... */ -#define SYMLINK_RECURSE_COUNT 20 - - static PHYSFS_sint64 ZIP_read(FileHandle *handle, void *buffer, PHYSFS_uint32 objSize, PHYSFS_uint32 objCount); static int ZIP_eof(FileHandle *handle); @@ -113,7 +131,6 @@ static int ZIP_seek(FileHandle *handle, PHYSFS_uint64 offset); static PHYSFS_sint64 ZIP_fileLength(FileHandle *handle); static int ZIP_fileClose(FileHandle *handle); static int ZIP_isArchive(const char *filename, int forWriting); -static char *get_zip_realpath(void *in, ZIPentry *entry); static DirHandle *ZIP_openArchive(const char *name, int forWriting); static LinkedStringList *ZIP_enumerateFiles(DirHandle *h, const char *dirname, @@ -125,6 +142,8 @@ static PHYSFS_sint64 ZIP_getLastModTime(DirHandle *h, const char *name); static FileHandle *ZIP_openRead(DirHandle *h, const char *filename); static void ZIP_dirClose(DirHandle *h); +static int zip_resolve(void *in, ZIPinfo *info, ZIPentry *entry); + static const FileFunctions __PHYSFS_FileFunctions_ZIP = { @@ -341,7 +360,7 @@ static int ZIP_seek(FileHandle *handle, PHYSFS_uint64 offset) * If seeking backwards, we need to redecode the file * from the start and throw away the compressed bits until we hit * the offset we need. If seeking forward, we still need to - * redecode, but we don't rewind first. + * decode, but we don't rewind first. */ if (offset < finfo->uncompressed_position) { @@ -385,7 +404,7 @@ static PHYSFS_sint64 ZIP_fileLength(FileHandle *handle) static int ZIP_fileClose(FileHandle *handle) { ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque); - __PHYSFS_platformClose(finfo->handle); + BAIL_IF_MACRO(!__PHYSFS_platformClose(finfo->handle), NULL, 0); if (finfo->entry->compression_method != COMPMETH_NONE) inflateEnd(&finfo->stream); @@ -398,16 +417,16 @@ static int ZIP_fileClose(FileHandle *handle) } /* ZIP_fileClose */ -static PHYSFS_sint64 find_end_of_central_dir(void *in, PHYSFS_sint64 *len) +static PHYSFS_sint64 zip_find_end_of_central_dir(void *in, PHYSFS_sint64 *len) { - /* !!! FIXME: potential race condition! */ - /* !!! FIXME: mutex this or switch to smaller stack-based buffer. */ - static PHYSFS_uint8 buf[0xFFFF + 20]; - + PHYSFS_uint8 buf[256]; PHYSFS_sint32 i; PHYSFS_sint64 filelen; PHYSFS_sint64 filepos; PHYSFS_sint32 maxread; + PHYSFS_sint32 totalread = 0; + int found = 0; + PHYSFS_uint32 extra; filelen = __PHYSFS_platformFileLength(in); BAIL_IF_MACRO(filelen == -1, NULL, 0); @@ -420,14 +439,10 @@ static PHYSFS_sint64 find_end_of_central_dir(void *in, PHYSFS_sint64 *len) * hit the end-of-central-dir signature. We can then sanity check that * the comment was as big as it should be to make sure we're in the * right place. The comment length field is 16 bits, so we can stop - * searching for that signature after 64k at most, and call it a - * corrupted zipfile. + * searching for that signature after a little more than 64k at most, + * and call it a corrupted zipfile. */ - /* - * !!! FIXME: This was easier than reading backwards in chunks, but it's - * !!! FIXME: rather memory hungry. - */ if (sizeof (buf) < filelen) { filepos = filelen - sizeof (buf); @@ -439,27 +454,52 @@ static PHYSFS_sint64 find_end_of_central_dir(void *in, PHYSFS_sint64 *len) maxread = filelen; } /* else */ - BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, filepos), NULL, -1); - BAIL_IF_MACRO(__PHYSFS_platformRead(in, buf, maxread, 1) != 1, NULL, -1); - - for (i = maxread - 4; i > 0; i--) + while ((totalread < filelen) && (totalread < 65557)) { - if ((buf[i + 0] == 0x50) && - (buf[i + 1] == 0x4B) && - (buf[i + 2] == 0x05) && - (buf[i + 3] == 0x06) ) - { - break; /* that's the signature! */ - } /* if */ - } /* for */ + BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, filepos), NULL, -1); - BAIL_IF_MACRO(i < 0, ERR_NOT_AN_ARCHIVE, -1); + /* make sure we catch a signature between buffers. */ + if (totalread != 0) + { + if (__PHYSFS_platformRead(in, buf, maxread - 4, 1) != 1) + return(-1); + *((PHYSFS_uint32 *) (&buf[maxread - 4])) = extra; + totalread += maxread - 4; + } /* if */ + else + { + if (__PHYSFS_platformRead(in, buf, maxread, 1) != 1) + return(-1); + totalread += maxread; + } /* else */ + + extra = *((PHYSFS_uint32 *) (&buf[0])); + + for (i = maxread - 4; i > 0; i--) + { + if ((buf[i + 0] == 0x50) && + (buf[i + 1] == 0x4B) && + (buf[i + 2] == 0x05) && + (buf[i + 3] == 0x06) ) + { + found = 1; /* that's the signature! */ + break; + } /* if */ + } /* for */ + + if (found) + break; + + filepos -= (maxread - 4); + } /* while */ + + BAIL_IF_MACRO(!found, ERR_NOT_AN_ARCHIVE, -1); if (len != NULL) *len = filelen; - return(filelen - (maxread - i)); -} /* find_end_of_central_dir */ + return(filepos + i); +} /* zip_find_end_of_central_dir */ static int ZIP_isArchive(const char *filename, int forWriting) @@ -484,7 +524,7 @@ static int ZIP_isArchive(const char *filename, int forWriting) * (a self-extracting executable, etc), so we'll have to do * it the hard way... */ - retval = (find_end_of_central_dir(in, NULL) == -1); + retval = (zip_find_end_of_central_dir(in, NULL) == -1); } /* if */ __PHYSFS_platformClose(in); @@ -492,91 +532,163 @@ static int ZIP_isArchive(const char *filename, int forWriting) } /* ZIP_isArchive */ -static int zip_set_open_position(void *in, ZIPentry *entry) +static void zip_free_entries(ZIPentry *entries, PHYSFS_uint32 max) { - /* - * We fix up the offset to point to the actual data on the - * first open, since we don't want to seek across the whole file on - * archive open (can be SLOW on large, CD-stored files), but we - * need to check the local file header...not just for corruption, - * but since it stores offset info the central directory does not. - */ - if (!entry->fixed_up) + PHYSFS_uint32 i; + for (i = 0; i < max; i++) { - PHYSFS_uint32 ui32; - PHYSFS_uint16 ui16; - PHYSFS_uint16 fnamelen; - PHYSFS_uint16 extralen; - - BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, entry->offset), NULL, 0); - BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); - BAIL_IF_MACRO(ui32 != ZIP_LOCAL_FILE_SIG, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); - BAIL_IF_MACRO(ui16 != entry->version_needed, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); /* general bits. */ - BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); - BAIL_IF_MACRO(ui16 != entry->compression_method, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); /* date/time */ - BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); - BAIL_IF_MACRO(ui32 != entry->crc, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); - BAIL_IF_MACRO(ui32 != entry->compressed_size, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); - BAIL_IF_MACRO(ui32 != entry->uncompressed_size, ERR_CORRUPTED, 0); - BAIL_IF_MACRO(!readui16(in, &fnamelen), NULL, 0); - BAIL_IF_MACRO(!readui16(in, &extralen), NULL, 0); - - entry->offset += fnamelen + extralen + 30; - entry->fixed_up = 1; - } /* if */ - - return(__PHYSFS_platformSeek(in, entry->offset)); -} /* zip_set_open_position */ - - - -static void freeEntries(ZIPinfo *info, int count) -{ - int i; - - for (i = 0; i < count; i++) - { - free(info->entries[i].name); - if (info->entries[i].symlink != NULL) - free(info->entries[i].symlink); + ZIPentry *entry = &entries[i]; + if (entry->name != NULL) + free(entry->name); } /* for */ - free(info->entries); -} /* freeEntries */ + free(entries); +} /* zip_free_entries */ -/* - * !!! FIXME: Really implement this. - * !!! FIXME: symlinks in zipfiles can be relative paths, including - * !!! FIXME: "." and ".." entries. These need to be parsed out. - * !!! FIXME: For now, though, we're just copying the relative path. Oh well. - */ -static char *expand_symlink_path(char *path, ZIPentry *entry) +static ZIPentry *zip_find_entry(ZIPinfo *info, const char *path) { + ZIPentry *a = info->entries; + PHYSFS_sint32 lo = 0; + PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1); + PHYSFS_sint32 middle; + int rc; + + while (lo <= hi) + { + middle = lo + ((hi - lo) / 2); + rc = strcmp(path, a[middle].name); + if (rc == 0) /* found it! */ + return(&a[middle]); + else if (rc > 0) + lo = middle + 1; + else + hi = middle - 1; + } /* while */ + + BAIL_MACRO(ERR_NO_SUCH_FILE, NULL); +} /* zip_find_entry */ + + +/* Convert paths from old, buggy DOS zippers... */ +static void zip_convert_dos_path(ZIPentry *entry, char *path) +{ + PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((entry->version >> 8) & 0xFF); + if (hosttype == 0) /* FS_FAT_ */ + { + while (*path) + { + if (*path == '\\') + *path = '/'; + path++; + } /* while */ + } /* if */ +} /* zip_convert_dos_path */ + + +static void zip_expand_symlink_path(char *path) +{ + char *ptr = path; + char *prevptr = path; + + while (1) + { + ptr = strchr(ptr, '/'); + if (ptr == NULL) + break; + + if (*(ptr + 1) == '.') + { + if (*(ptr + 2) == '/') + { + /* current dir in middle of string: ditch it. */ + memmove(ptr, ptr + 2, strlen(ptr + 2) + 1); + } /* else if */ + + else if (*(ptr + 2) == '\0') + { + /* current dir at end of string: ditch it. */ + *ptr = '\0'; + } /* else if */ + + else if (*(ptr + 2) == '.') + { + if (*(ptr + 3) == '/') + { + /* parent dir in middle: move back one, if possible. */ + memmove(prevptr, ptr + 4, strlen(ptr + 4) + 1); + ptr = prevptr; + while (prevptr != path) + { + prevptr--; + if (*prevptr == '/') + { + prevptr++; + break; + } /* if */ + } /* while */ + } /* if */ + + if (*(ptr + 3) == '\0') + { + /* parent dir at end: move back one, if possible. */ + *prevptr = '\0'; + } /* if */ + } /* if */ + } /* if */ + else + { + prevptr = ptr; + } /* else */ + } /* while */ +} /* zip_expand_symlink_path */ + + /* - char *retval = (char *) malloc(strlen(path) + 1); - BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL); - strcpy(retval, path); - return(retval); -*/ - return(path); /* !!! FIXME */ -} /* expand_symlink_path */ + * Look for the entry named by (path). If it exists, resolve it, and return + * a pointer to that entry. If it's another symlink, keep resolving until you + * hit a real file and then return a pointer to the final non-symlink entry. + * If there's a problem, return NULL. (path) is always free()'d by this + * function. + */ +static ZIPentry *zip_follow_symlink(void *in, ZIPinfo *info, char *path) +{ + ZIPentry *entry; + + zip_expand_symlink_path(path); + entry = zip_find_entry(info, path); + if (entry != NULL) + { + if (!zip_resolve(in, info, entry)) /* recursive! */ + entry = NULL; + else + { + if (entry->symlink != NULL) + entry = entry->symlink; + } /* else */ + } /* if */ + + free(path); + return(entry); +} /* zip_follow_symlink */ -static char *get_zip_realpath(void *in, ZIPentry *entry) +static int zip_resolve_symlink(void *in, ZIPinfo *info, ZIPentry *entry) { char *path; PHYSFS_uint32 size = entry->uncompressed_size; int rc = 0; - BAIL_IF_MACRO(!zip_set_open_position(in, entry), NULL, NULL); + /* + * We've already parsed the local file header of the symlink at this + * point. Now we need to read the actual link from the file data and + * follow it. + */ + + BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, entry->offset), NULL, 0); + path = (char *) malloc(size + 1); - BAIL_IF_MACRO(path == NULL, ERR_OUT_OF_MEMORY, NULL); + BAIL_IF_MACRO(path == NULL, ERR_OUT_OF_MEMORY, 0); if (entry->compression_method == COMPMETH_NONE) rc = (__PHYSFS_platformRead(in, path, size, 1) == 1); @@ -609,17 +721,97 @@ static char *get_zip_realpath(void *in, ZIPentry *entry) } /* else */ if (!rc) - { free(path); - return(NULL); + else + { + path[entry->uncompressed_size] = '\0'; /* null-terminate it. */ + zip_convert_dos_path(entry, path); + entry->symlink = zip_follow_symlink(in, info, path); + } /* else */ + + return(entry->symlink != NULL); +} /* zip_resolve_symlink */ + + +/* + * Parse the local file header of an entry, and update entry->offset. + */ +static int zip_parse_local(void *in, ZIPentry *entry) +{ + PHYSFS_uint32 ui32; + PHYSFS_uint16 ui16; + PHYSFS_uint16 fnamelen; + PHYSFS_uint16 extralen; + + BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, entry->offset), NULL, 0); + BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); + BAIL_IF_MACRO(ui32 != ZIP_LOCAL_FILE_SIG, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); + BAIL_IF_MACRO(ui16 != entry->version_needed, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); /* general bits. */ + BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); + BAIL_IF_MACRO(ui16 != entry->compression_method, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); /* date/time */ + BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); + BAIL_IF_MACRO(ui32 != entry->crc, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); + BAIL_IF_MACRO(ui32 != entry->compressed_size, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); + BAIL_IF_MACRO(ui32 != entry->uncompressed_size, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(!readui16(in, &fnamelen), NULL, 0); + BAIL_IF_MACRO(!readui16(in, &extralen), NULL, 0); + + entry->offset += fnamelen + extralen + 30; + return(1); +} /* zip_parse_local */ + + +static int zip_resolve(void *in, ZIPinfo *info, ZIPentry *entry) +{ + int retval = 1; + ZipResolveType resolve_type = entry->resolved; + + /* Don't bother if we've failed to resolve this entry before. */ + BAIL_IF_MACRO(resolve_type == ZIP_BROKEN_FILE, ERR_CORRUPTED, 0); + BAIL_IF_MACRO(resolve_type == ZIP_BROKEN_SYMLINK, ERR_CORRUPTED, 0); + + /* uhoh...infinite symlink loop! */ + BAIL_IF_MACRO(resolve_type == ZIP_RESOLVING, ERR_SYMLINK_LOOP, 0); + + /* + * We fix up the offset to point to the actual data on the + * first open, since we don't want to seek across the whole file on + * archive open (can be SLOW on large, CD-stored files), but we + * need to check the local file header...not just for corruption, + * but since it stores offset info the central directory does not. + */ + if (resolve_type != ZIP_RESOLVED) + { + entry->resolved = ZIP_RESOLVING; + + retval = zip_parse_local(in, entry); + if (retval) + { + /* + * If it's a symlink, find the original file. This will cause + * resolution of other entries (other symlinks and, eventually, + * the real file) if all goes well. + */ + if (resolve_type == ZIP_UNRESOLVED_SYMLINK) + retval = zip_resolve_symlink(in, info, entry); + } /* if */ + + if (resolve_type == ZIP_UNRESOLVED_SYMLINK) + entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_SYMLINK); + else if (resolve_type == ZIP_UNRESOLVED_FILE) + entry->resolved = ((retval) ? ZIP_RESOLVED : ZIP_BROKEN_FILE); } /* if */ - path[entry->uncompressed_size] = '\0'; /* null-terminate it. */ - return(expand_symlink_path(path, entry)); /* retval is realloc()'d path. */ -} /* get_zip_realpath */ + return(retval); +} /* zip_resolve */ -static int version_does_symlinks(PHYSFS_uint32 version) +static int zip_version_does_symlinks(PHYSFS_uint32 version) { int retval = 0; PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((version >> 8) & 0xFF); @@ -648,40 +840,56 @@ static int version_does_symlinks(PHYSFS_uint32 version) } /* switch */ return(retval); -} /* version_does_symlinks */ +} /* zip_version_does_symlinks */ -static int entry_is_symlink(ZIPentry *entry, PHYSFS_uint32 extern_attr) +static int zip_entry_is_symlink(ZIPentry *entry) +{ + return((entry->resolved == ZIP_UNRESOLVED_SYMLINK) || + (entry->resolved == ZIP_BROKEN_SYMLINK) || + (entry->symlink)); +} /* zip_entry_is_symlink */ + + +static int zip_has_symlink_attr(ZIPentry *entry, PHYSFS_uint32 extern_attr) { PHYSFS_uint16 xattr = ((extern_attr >> 16) & 0xFFFF); return ( - (version_does_symlinks(entry->version)) && + (zip_version_does_symlinks(entry->version)) && (entry->uncompressed_size > 0) && ((xattr & UNIX_FILETYPE_MASK) == UNIX_FILETYPE_SYMLINK) ); -} /* entry_is_symlink */ +} /* zip_has_symlink_attr */ -PHYSFS_sint64 dos_time_to_physfs_time(PHYSFS_uint32 dostime) +PHYSFS_sint64 zip_dos_time_to_physfs_time(PHYSFS_uint32 dostime) { - PHYSFS_uint32 dosdate = (PHYSFS_uint32) (dostime >> 16); + PHYSFS_uint32 dosdate; struct tm unixtime; memset(&unixtime, '\0', sizeof (unixtime)); - unixtime.tm_mday = (PHYSFS_uint32) (dosdate & 0x1F); - unixtime.tm_mon = (PHYSFS_uint32) ((((dosdate) & 0x1E0) / 0x20) - 1); - unixtime.tm_year = (PHYSFS_uint32) (((dosdate & 0x0FE00) / 0x0200) + 80); + dosdate = (PHYSFS_uint32) ((dostime >> 16) & 0xFFFF); + dostime &= 0xFFFF; - unixtime.tm_hour = (PHYSFS_uint32) ((dostime & 0xF800) / 0x800); - unixtime.tm_min = (PHYSFS_uint32) ((dostime & 0x7E0) / 0x20); - unixtime.tm_sec = (PHYSFS_uint32) (2 * (dostime & 0x1F)); + /* dissect date */ + unixtime.tm_year = ((dosdate >> 9) & 0x7F) + 80; + unixtime.tm_mon = ((dosdate >> 5) & 0x0F) - 1; + unixtime.tm_mday = ((dosdate ) & 0x1F); + + /* dissect time */ + unixtime.tm_hour = ((dostime >> 11) & 0x1F); + unixtime.tm_min = ((dostime >> 5) & 0x3F); + unixtime.tm_sec = ((dostime << 1) & 0x3E); + + /* let mktime calculate daylight savings time. */ + unixtime.tm_isdst = -1; return((PHYSFS_sint64) mktime(&unixtime)); -} /* dos_time_to_physfs_time */ +} /* zip_dos_time_to_physfs_time */ -static int load_zip_entry(void *in, ZIPentry *entry, PHYSFS_uint32 ofs_fixup) +static int zip_load_entry(void *in, ZIPentry *entry, PHYSFS_uint32 ofs_fixup) { PHYSFS_uint16 fnamelen, extralen, commentlen; PHYSFS_uint32 external_attr; @@ -696,10 +904,10 @@ static int load_zip_entry(void *in, ZIPentry *entry, PHYSFS_uint32 ofs_fixup) /* Get the pertinent parts of the record... */ BAIL_IF_MACRO(!readui16(in, &entry->version), NULL, 0); BAIL_IF_MACRO(!readui16(in, &entry->version_needed), NULL, 0); - BAIL_IF_MACRO(!readui16(in, &entry->flag), NULL, 0); + BAIL_IF_MACRO(!readui16(in, &ui16), NULL, 0); /* general bits */ BAIL_IF_MACRO(!readui16(in, &entry->compression_method), NULL, 0); BAIL_IF_MACRO(!readui32(in, &ui32), NULL, 0); - entry->last_mod_time = dos_time_to_physfs_time(ui32); + entry->last_mod_time = zip_dos_time_to_physfs_time(ui32); BAIL_IF_MACRO(!readui32(in, &entry->crc), NULL, 0); BAIL_IF_MACRO(!readui32(in, &entry->compressed_size), NULL, 0); BAIL_IF_MACRO(!readui32(in, &entry->uncompressed_size), NULL, 0); @@ -711,42 +919,110 @@ static int load_zip_entry(void *in, ZIPentry *entry, PHYSFS_uint32 ofs_fixup) BAIL_IF_MACRO(!readui32(in, &external_attr), NULL, 0); BAIL_IF_MACRO(!readui32(in, &entry->offset), NULL, 0); entry->offset += ofs_fixup; - entry->fixed_up = 0; /* we need to do a second fixup at openRead(). */ + + entry->symlink = NULL; /* will be resolved later, if necessary. */ + entry->resolved = (zip_has_symlink_attr(entry, external_attr)) ? + ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE; entry->name = (char *) malloc(fnamelen + 1); BAIL_IF_MACRO(entry->name == NULL, ERR_OUT_OF_MEMORY, 0); if (__PHYSFS_platformRead(in, entry->name, fnamelen, 1) != 1) - goto load_zip_entry_puked; + goto zip_load_entry_puked; entry->name[fnamelen] = '\0'; /* null-terminate the filename. */ + zip_convert_dos_path(entry, entry->name); si64 = __PHYSFS_platformTell(in); if (si64 == -1) - goto load_zip_entry_puked; - - /* If this is a symlink, resolve it. */ - if (!entry_is_symlink(entry, external_attr)) - entry->symlink = NULL; - else - { - entry->symlink = get_zip_realpath(in, entry); - if (entry->symlink == NULL) - goto load_zip_entry_puked; - } /* else */ + goto zip_load_entry_puked; /* seek to the start of the next entry in the central directory... */ if (!__PHYSFS_platformSeek(in, si64 + extralen + commentlen)) - goto load_zip_entry_puked; + goto zip_load_entry_puked; return(1); /* success. */ -load_zip_entry_puked: +zip_load_entry_puked: free(entry->name); return(0); /* failure. */ -} /* load_zip_entry */ +} /* zip_load_entry */ -static int load_zip_entries(void *in, DirHandle *dirh, +static void zip_entry_swap(ZIPentry *a, PHYSFS_uint32 one, PHYSFS_uint32 two) +{ + ZIPentry tmp; + memcpy(&tmp, &a[one], sizeof (ZIPentry)); + memcpy(&a[one], &a[two], sizeof (ZIPentry)); + memcpy(&a[two], &tmp, sizeof (ZIPentry)); +} /* zip_entry_swap */ + + +static void zip_quick_sort(ZIPentry *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi) +{ + PHYSFS_uint32 i; + PHYSFS_uint32 j; + ZIPentry *v; + + if ((hi - lo) > ZIP_QUICKSORT_THRESHOLD) + { + i = (hi + lo) / 2; + + if (strcmp(a[lo].name, a[i].name) > 0) zip_entry_swap(a, lo, i); + if (strcmp(a[lo].name, a[hi].name) > 0) zip_entry_swap(a, lo, hi); + if (strcmp(a[i].name, a[hi].name) > 0) zip_entry_swap(a, i, hi); + + j = hi - 1; + zip_entry_swap(a, i, j); + i = lo; + v = &a[j]; + while (1) + { + while(strcmp(a[++i].name, v->name) < 0) {} + while(strcmp(a[--j].name, v->name) > 0) {} + if (j < i) + break; + zip_entry_swap(a, i, j); + } /* while */ + zip_entry_swap(a, i, hi-1); + zip_quick_sort(a, lo, j); + zip_quick_sort(a, i+1, hi); + } /* if */ +} /* zip_quick_sort */ + + +static void zip_insertion_sort(ZIPentry *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi) +{ + PHYSFS_uint32 i; + PHYSFS_uint32 j; + ZIPentry tmp; + + for (i = lo + 1; i <= hi; i++) + { + memcpy(&tmp, &a[i], sizeof (ZIPentry)); + j = i; + while ((j > lo) && (strcmp(a[j - 1].name, tmp.name) > 0)) + { + memcpy(&a[j], &a[j - 1], sizeof (ZIPentry)); + j--; + } /* while */ + memcpy(&a[j], &tmp, sizeof (ZIPentry)); + } /* for */ +} /* zip_insertion_sort */ + + +static void zip_sort_entries(ZIPentry *entries, PHYSFS_uint32 max) +{ + /* + * Fast Quicksort algorithm inspired by code from here: + * http://www.cs.ubc.ca/spider/harrison/Java/sorting-demo.html + */ + if (max <= ZIP_QUICKSORT_THRESHOLD) + zip_quick_sort(entries, 0, max - 1); + zip_insertion_sort(entries, 0, max - 1); +} /* zip_sort_entries */ + + +static int zip_load_entries(void *in, DirHandle *dirh, PHYSFS_uint32 data_ofs, PHYSFS_uint32 central_ofs) { ZIPinfo *info = (ZIPinfo *) dirh->opaque; @@ -760,20 +1036,21 @@ static int load_zip_entries(void *in, DirHandle *dirh, for (i = 0; i < max; i++) { - if (!load_zip_entry(in, &info->entries[i], data_ofs)) + if (!zip_load_entry(in, &info->entries[i], data_ofs)) { - freeEntries(info, i); + zip_free_entries(info->entries, i); return(0); } /* if */ } /* for */ + zip_sort_entries(info->entries, max); return(1); -} /* load_zip_entries */ +} /* zip_load_entries */ -static int parse_end_of_central_dir(void *in, DirHandle *dirh, - PHYSFS_uint32 *data_start, - PHYSFS_uint32 *central_dir_ofs) +static int zip_parse_end_of_central_dir(void *in, DirHandle *dirh, + PHYSFS_uint32 *data_start, + PHYSFS_uint32 *central_dir_ofs) { ZIPinfo *zipinfo = (ZIPinfo *) dirh->opaque; PHYSFS_uint32 ui32; @@ -782,7 +1059,7 @@ static int parse_end_of_central_dir(void *in, DirHandle *dirh, PHYSFS_sint64 pos; /* find the end-of-central-dir record, and seek to it. */ - pos = find_end_of_central_dir(in, &len); + pos = zip_find_end_of_central_dir(in, &len); BAIL_IF_MACRO(pos == -1, NULL, 0); BAIL_IF_MACRO(!__PHYSFS_platformSeek(in, pos), NULL, 0); @@ -836,41 +1113,41 @@ static int parse_end_of_central_dir(void *in, DirHandle *dirh, BAIL_IF_MACRO((pos + 22 + ui16) != len, ERR_UNSUPPORTED_ARCHIVE, 0); return(1); /* made it. */ -} /* parse_end_of_central_dir */ +} /* zip_parse_end_of_central_dir */ -static DirHandle *allocate_zip_dirhandle(const char *name) +static DirHandle *zip_allocate_dirhandle(const char *name) { char *ptr; + ZIPinfo *info; DirHandle *retval = malloc(sizeof (DirHandle)); BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL); memset(retval, '\0', sizeof (DirHandle)); - retval->opaque = malloc(sizeof (ZIPinfo)); - if (retval->opaque == NULL) + info = (ZIPinfo *) malloc(sizeof (ZIPinfo)); + if (info == NULL) { free(retval); BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); } /* if */ - memset(retval->opaque, '\0', sizeof (ZIPinfo)); + memset(info, '\0', sizeof (ZIPinfo)); ptr = (char *) malloc(strlen(name) + 1); if (ptr == NULL) { - free(retval->opaque); + free(info); free(retval); BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); } /* if */ - ((ZIPinfo *) (retval->opaque))->archiveName = ptr; - strcpy(((ZIPinfo *) (retval->opaque))->archiveName, name); - + info->archiveName = ptr; + strcpy(info->archiveName, name); + retval->opaque = info; retval->funcs = &__PHYSFS_DirFunctions_ZIP; - return(retval); -} /* allocate_zip_dirhandle */ +} /* zip_allocate_dirhandle */ static DirHandle *ZIP_openArchive(const char *name, int forWriting) @@ -878,7 +1155,7 @@ static DirHandle *ZIP_openArchive(const char *name, int forWriting) DirHandle *retval = NULL; void *in = NULL; PHYSFS_uint32 data_start; - PHYSFS_uint32 central_dir_ofs; + PHYSFS_uint32 cent_dir_ofs; int success = 0; BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, NULL); @@ -886,13 +1163,13 @@ static DirHandle *ZIP_openArchive(const char *name, int forWriting) if ((in = __PHYSFS_platformOpenRead(name)) == NULL) goto zip_openarchive_end; - if ((retval = allocate_zip_dirhandle(name)) == NULL) + if ((retval = zip_allocate_dirhandle(name)) == NULL) goto zip_openarchive_end; - if (!parse_end_of_central_dir(in, retval, &data_start, ¢ral_dir_ofs)) + if (!zip_parse_end_of_central_dir(in, retval, &data_start, ¢_dir_ofs)) goto zip_openarchive_end; - if (!load_zip_entries(in, retval, data_start, central_dir_ofs)) + if (!zip_load_entries(in, retval, data_start, cent_dir_ofs)) goto zip_openarchive_end; success = 1; /* ...and we're good to go. :) */ @@ -922,243 +1199,204 @@ zip_openarchive_end: } /* ZIP_openArchive */ -/* !!! FIXME: This is seriously ugly. */ +static PHYSFS_sint32 zip_find_start_of_dir(ZIPinfo *info, const char *path, + int stop_on_first_find) +{ + PHYSFS_sint32 lo = 0; + PHYSFS_sint32 hi = (PHYSFS_sint32) info->entryCount; + PHYSFS_sint32 middle; + PHYSFS_uint32 dlen = strlen(path); + PHYSFS_sint32 retval = -1; + const char *name; + int rc; + + if (*path == '\0') /* root dir? */ + return(0); + + if ((dlen > 0) && (path[dlen - 1] == '/')) /* ignore trailing slash. */ + dlen--; + + while (lo <= hi) + { + middle = lo + ((hi - lo) / 2); + name = info->entries[middle].name; + rc = strncmp(path, name, dlen); + if (rc == 0) + { + char ch = name[dlen]; + if (ch < '/') /* make sure this isn't just a substr match. */ + rc = -1; + else if (ch > '/') + rc = 1; + else + { + if ((name[dlen + 1] == '\0') || (stop_on_first_find)) + return(middle); + + /* there might be more entries earlier in the list. */ + retval = middle; + hi = middle - 1; + } /* else */ + } /* if */ + + if (rc > 0) + lo = middle + 1; + else + hi = middle - 1; + } /* while */ + + return(retval); +} /* zip_find_start_of_dir */ + + static LinkedStringList *ZIP_enumerateFiles(DirHandle *h, const char *dirname, int omitSymLinks) { - ZIPinfo *zi = (ZIPinfo *) (h->opaque); - unsigned int i; - int dlen; - LinkedStringList *retval = NULL; - LinkedStringList *l = NULL; - LinkedStringList *prev = NULL; - char *d; - ZIPentry *entry; - char buf[MAXZIPENTRYSIZE]; + ZIPinfo *info = ((ZIPinfo *) h->opaque); + PHYSFS_sint32 i, max = (PHYSFS_sint32) info->entryCount; + LinkedStringList *retval = NULL, *p = NULL; + PHYSFS_uint32 dlen = strlen(dirname); - dlen = strlen(dirname); - d = malloc(dlen + 1); - BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, NULL); - strcpy(d, dirname); - if ((dlen > 0) && (d[dlen - 1] == '/')) /* remove trailing slash. */ + i = zip_find_start_of_dir(info, dirname, 0); + BAIL_IF_MACRO(i == -1, ERR_NO_SUCH_FILE, NULL); + + while (1) { - dlen--; - d[dlen] = '\0'; - } /* if */ + ZIPentry *entry = &info->entries[i]; + const char *add_file; + size_t strsize; + char *slash; - for (i = 0, entry = zi->entries; i < zi->entryCount; i++, entry++) - { - char *ptr; - char *add_file; - int this_dlen; - - if ((omitSymLinks) && (entry->symlink != NULL)) - continue; - - this_dlen = strlen(entry->name); - if (this_dlen + 1 > MAXZIPENTRYSIZE) /* !!! FIXME */ - continue; /* ugh. */ - - strcpy(buf, entry->name); - - if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */ + add_file = entry->name + dlen + ((dlen > 0) ? 1 : 0); + if ( ((omitSymLinks) && (zip_entry_is_symlink(entry))) || + (*add_file == '\0') ) /* skip links and the dir entry itself. */ { - this_dlen--; - buf[this_dlen] = '\0'; + i++; + continue; } /* if */ - if (this_dlen <= dlen) /* not in this dir. */ - continue; + slash = strchr(add_file, '/'); /* handle subdirs under dirname... */ + strsize = (size_t) ((slash) ? (slash - add_file) : strlen(add_file)); - if (*d == '\0') - add_file = buf; - else + retval = __PHYSFS_addToLinkedStringList(retval, &p, add_file, strsize); + + if (++i >= max) + break; /* we're at the end of the entries array. */ + + if ((dlen > 0) && (strncmp(info->entries[i].name, dirname, dlen) != 0)) + break; /* we're past this dir's entries. */ + + /* We added a subdir? Skip its children. */ + while (slash != NULL) { - if (buf[dlen] != '/') /* can't be in same directory? */ - continue; - - buf[dlen] = '\0'; - if (__PHYSFS_platformStricmp(d, buf) != 0) /* not same directory? */ - continue; - - add_file = buf + dlen + 1; - } /* else */ - - /* handle subdirectories... */ - ptr = strchr(add_file, '/'); - if (ptr != NULL) - { - LinkedStringList *j; - *ptr = '\0'; - for (j = retval; j != NULL; j = j->next) + if (strncmp(info->entries[i].name, dirname, dlen) == 0) { - if (__PHYSFS_platformStricmp(j->str, ptr) == 0) - break; - } /* for */ + if (info->entries[i].name[dlen] == '/') + { + i++; /* skip it. */ + continue; + } /* if */ + } /* if */ + slash = NULL; + } /* while */ + } /* while */ - if (j != NULL) - continue; - } /* if */ - - l = (LinkedStringList *) malloc(sizeof (LinkedStringList)); - if (l == NULL) - break; - - l->str = (char *) malloc(strlen(add_file) + 1); - if (l->str == NULL) - { - free(l); - break; - } /* if */ - - strcpy(l->str, add_file); - - if (retval == NULL) - retval = l; - else - prev->next = l; - - prev = l; - l->next = NULL; - } /* for */ - - free(d); return(retval); } /* ZIP_enumerateFiles */ -/* !!! FIXME: This is seriously ugly. */ -static int ZIP_exists_symcheck(DirHandle *h, const char *name, int follow) -{ - char buf[MAXZIPENTRYSIZE]; - ZIPinfo *zi = (ZIPinfo *) (h->opaque); - int dlen; - char *d; - unsigned int i; - ZIPentry *entry; - - dlen = strlen(name); - d = malloc(dlen + 1); - BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, -1); - strcpy(d, name); - if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */ - { - dlen--; - d[dlen] = '\0'; - } /* if */ - - for (i = 0, entry = zi->entries; i < zi->entryCount; i++, entry++) - { - int this_dlen = strlen(entry->name); - if (this_dlen + 1 > MAXZIPENTRYSIZE) - continue; /* ugh. */ - - strcpy(buf, entry->name); - if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */ - { - this_dlen--; - buf[this_dlen] = '\0'; - } /* if */ - - if ( ((buf[dlen] == '/') || (buf[dlen] == '\0')) && - (strncmp(d, buf, dlen) == 0) ) - { - int retval = i; - free(d); - if (follow) /* follow symlinks? */ - { - if (entry->symlink != NULL) - retval = ZIP_exists_symcheck(h, entry->symlink, follow-1); - } /* if */ - return(retval); - } /* if */ - } /* for */ - - free(d); - return(-1); -} /* ZIP_exists_symcheck */ - - static int ZIP_exists(DirHandle *h, const char *name) { - int pos = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT); - int is_sym; - - BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, 0); - - /* if it's a symlink, then we ran into a possible symlink loop. */ - is_sym = ( ((ZIPinfo *)(h->opaque))->entries[pos].symlink != NULL ); - BAIL_IF_MACRO(is_sym, ERR_SYMLINK_LOOP, 0); - - return(1); + ZIPentry *entry = zip_find_entry((ZIPinfo *) h->opaque, name); + return(entry != NULL); } /* ZIP_exists */ static PHYSFS_sint64 ZIP_getLastModTime(DirHandle *h, const char *name) { - ZIPinfo *zi = (ZIPinfo *) (h->opaque); - int pos = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT); - ZIPentry *entry; - - BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, 0); - - entry = &zi->entries[pos]; - - /* if it's a symlink, then we ran into a possible symlink loop. */ - BAIL_IF_MACRO(entry->symlink != NULL, ERR_SYMLINK_LOOP, 0); - + ZIPentry *entry = zip_find_entry((ZIPinfo *) h->opaque, name); + BAIL_IF_MACRO(entry == NULL, NULL, -1); return(entry->last_mod_time); } /* ZIP_getLastModTime */ static int ZIP_isDirectory(DirHandle *h, const char *name) { - int dlen; - int is_sym; - int pos = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT); + ZIPinfo *info = (ZIPinfo *) h->opaque; + PHYSFS_uint32 pos; + ZIPentry *entry; - BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, 0); + pos = zip_find_start_of_dir(info, name, 1); + if (pos >= 0) + return(1); /* definitely a dir. */ - /* if it's a symlink, then we ran into a possible symlink loop. */ - is_sym = ( ((ZIPinfo *)(h->opaque))->entries[pos].symlink != NULL ); - BAIL_IF_MACRO(is_sym, ERR_SYMLINK_LOOP, 0); + /* Follow symlinks. This means we might need to resolve entries. */ + entry = zip_find_entry(info, name); + BAIL_IF_MACRO(entry == NULL, ERR_NO_SUCH_FILE, 0); - dlen = strlen(name); + if (entry->resolved == ZIP_UNRESOLVED_SYMLINK) /* gotta resolve it. */ + { + int rc; + void *in = __PHYSFS_platformOpenRead(info->archiveName); + BAIL_IF_MACRO(in == NULL, NULL, 0); + rc = zip_resolve(in, info, entry); + __PHYSFS_platformClose(in); + if (!rc) + return(0); + } /* if */ - /* !!! FIXME: yikes. Better way to check? */ - return((((ZIPinfo *)(h->opaque))->entries[pos].name[dlen] == '/')); + BAIL_IF_MACRO(entry->resolved == ZIP_BROKEN_SYMLINK, NULL, 0); + BAIL_IF_MACRO(entry->symlink == NULL, ERR_NOT_A_DIR, 0); + + return(zip_find_start_of_dir(info, entry->symlink->name, 1) >= 0); } /* ZIP_isDirectory */ static int ZIP_isSymLink(DirHandle *h, const char *name) { - int pos = ZIP_exists_symcheck(h, name, 0); - BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, 0); - return( ((ZIPinfo *)(h->opaque))->entries[pos].symlink != NULL ); + ZIPentry *entry = zip_find_entry((ZIPinfo *) h->opaque, name); + BAIL_IF_MACRO(entry == NULL, NULL, 0); + return(zip_entry_is_symlink(entry)); } /* ZIP_isSymLink */ +static void *zip_get_file_handle(const char *fn, ZIPinfo *inf, ZIPentry *entry) +{ + int success; + void *retval = __PHYSFS_platformOpenRead(fn); + BAIL_IF_MACRO(retval == NULL, NULL, NULL); + + success = zip_resolve(retval, inf, entry); + if (success) + { + PHYSFS_sint64 offset; + offset = ((entry->symlink) ? entry->symlink->offset : entry->offset); + success = __PHYSFS_platformSeek(retval, offset); + } /* if */ + + if (!success) + { + __PHYSFS_platformClose(retval); + retval = NULL; + } /* if */ + + return(retval); +} /* zip_get_file_handle */ + + static FileHandle *ZIP_openRead(DirHandle *h, const char *filename) { + ZIPinfo *info = (ZIPinfo *) h->opaque; + ZIPentry *entry = zip_find_entry(info, filename); FileHandle *retval = NULL; - ZIPinfo *zi = ((ZIPinfo *) (h->opaque)); ZIPfileinfo *finfo = NULL; - int pos = ZIP_exists_symcheck(h, filename, SYMLINK_RECURSE_COUNT); void *in; - BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, NULL); + BAIL_IF_MACRO(entry == NULL, NULL, NULL); - /* if it's a symlink, then we ran into a possible symlink loop. */ - BAIL_IF_MACRO(zi->entries[pos].symlink != NULL, ERR_SYMLINK_LOOP, 0); - - in = __PHYSFS_platformOpenRead(zi->archiveName); - BAIL_IF_MACRO(in == NULL, ERR_IO_ERROR, NULL); - if (!zip_set_open_position(in, &zi->entries[pos])) - { - __PHYSFS_platformClose(in); - return(0); - } /* if */ + in = zip_get_file_handle(info->archiveName, info, entry); + BAIL_IF_MACRO(in == NULL, NULL, NULL); if ( ((retval = (FileHandle *) malloc(sizeof (FileHandle))) == NULL) || ((finfo = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo))) == NULL) ) @@ -1166,7 +1404,7 @@ static FileHandle *ZIP_openRead(DirHandle *h, const char *filename) if (retval) free(retval); __PHYSFS_platformClose(in); - BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL); + BAIL_MACRO(ERR_OUT_OF_MEMORY, NULL); } /* if */ retval->opaque = (void *) finfo; @@ -1175,7 +1413,7 @@ static FileHandle *ZIP_openRead(DirHandle *h, const char *filename) memset(finfo, '\0', sizeof (ZIPfileinfo)); finfo->handle = in; - finfo->entry = &zi->entries[pos]; + finfo->entry = ((entry->symlink != NULL) ? entry->symlink : entry); if (finfo->entry->compression_method != COMPMETH_NONE) { if (zlib_err(inflateInit2(&finfo->stream, -MAX_WBITS)) != Z_OK) @@ -1199,7 +1437,8 @@ static FileHandle *ZIP_openRead(DirHandle *h, const char *filename) static void ZIP_dirClose(DirHandle *h) { ZIPinfo *zi = (ZIPinfo *) (h->opaque); - freeEntries(zi, zi->entryCount); + zip_free_entries(zi->entries, zi->entryCount); + free(zi->entries); free(zi->archiveName); free(zi); free(h);