diff -r ea891871f481 -r bb5522e88ab2 misc/libphysfs/archiver_zip.c --- a/misc/libphysfs/archiver_zip.c Mon Apr 10 09:05:16 2017 -0400 +++ b/misc/libphysfs/archiver_zip.c Mon Apr 10 12:06:43 2017 -0400 @@ -15,12 +15,7 @@ #include #include -#define USE_MINIZ 1 -#if USE_MINIZ #include "physfs_miniz.h" -#else -#include -#endif /* * A buffer of ZIP_READBUFSIZE is allocated for each compressed file opened, @@ -51,6 +46,7 @@ ZIP_UNRESOLVED_SYMLINK, ZIP_RESOLVING, ZIP_RESOLVED, + ZIP_DIRECTORY, ZIP_BROKEN_FILE, ZIP_BROKEN_SYMLINK } ZipResolveType; @@ -67,11 +63,16 @@ PHYSFS_uint64 offset; /* offset of data in archive */ PHYSFS_uint16 version; /* version made by */ PHYSFS_uint16 version_needed; /* version needed to extract */ + PHYSFS_uint16 general_bits; /* general purpose bits */ PHYSFS_uint16 compression_method; /* compression method */ PHYSFS_uint32 crc; /* crc-32 */ PHYSFS_uint64 compressed_size; /* compressed size */ PHYSFS_uint64 uncompressed_size; /* uncompressed size */ PHYSFS_sint64 last_mod_time; /* last file mod time */ + PHYSFS_uint32 dos_mod_time; /* original MS-DOS style mod time */ + struct _ZIPentry *hashnext; /* next item in this hash bucket */ + struct _ZIPentry *children; /* linked list of kids, if dir */ + struct _ZIPentry *sibling; /* next item in same dir */ } ZIPentry; /* @@ -79,10 +80,12 @@ */ typedef struct { - PHYSFS_Io *io; - int zip64; /* non-zero if this is a Zip64 archive. */ - PHYSFS_uint64 entryCount; /* Number of files in ZIP. */ - ZIPentry *entries; /* info on all files in ZIP. */ + PHYSFS_Io *io; /* the i/o interface for this archive. */ + ZIPentry root; /* root of directory tree. */ + ZIPentry **hash; /* all entries hashed for fast lookup. */ + size_t hashBuckets; /* number of buckets in hash. */ + int zip64; /* non-zero if this is a Zip64 archive. */ + int has_crypto; /* non-zero if any entry uses encryption. */ } ZIPinfo; /* @@ -95,6 +98,8 @@ PHYSFS_uint32 compressed_position; /* offset in compressed data. */ PHYSFS_uint32 uncompressed_position; /* tell() position. */ PHYSFS_uint8 *buffer; /* decompression buffer. */ + PHYSFS_uint32 crypto_keys[3]; /* for "traditional" crypto. */ + PHYSFS_uint32 initial_crypto_keys[3]; /* for "traditional" crypto. */ z_stream stream; /* zlib stream state. */ } ZIPfileinfo; @@ -115,6 +120,103 @@ #define UNIX_FILETYPE_MASK 0170000 #define UNIX_FILETYPE_SYMLINK 0120000 +#define ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO (1 << 0) +#define ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER (1 << 3) + +/* support for "traditional" PKWARE encryption. */ +static int zip_entry_is_tradional_crypto(const ZIPentry *entry) +{ + return (entry->general_bits & ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO) != 0; +} /* zip_entry_is_traditional_crypto */ + +static int zip_entry_ignore_local_header(const ZIPentry *entry) +{ + return (entry->general_bits & ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER) != 0; +} /* zip_entry_is_traditional_crypto */ + +static PHYSFS_uint32 zip_crypto_crc32(const PHYSFS_uint32 crc, const PHYSFS_uint8 val) +{ + int i; + PHYSFS_uint32 xorval = (crc ^ ((PHYSFS_uint32) val)) & 0xFF; + for (i = 0; i < 8; i++) + xorval = ((xorval & 1) ? (0xEDB88320 ^ (xorval >> 1)) : (xorval >> 1)); + return xorval ^ (crc >> 8); +} /* zip_crc32 */ + +static void zip_update_crypto_keys(PHYSFS_uint32 *keys, const PHYSFS_uint8 val) +{ + keys[0] = zip_crypto_crc32(keys[0], val); + keys[1] = keys[1] + (keys[0] & 0x000000FF); + keys[1] = (keys[1] * 134775813) + 1; + keys[2] = zip_crypto_crc32(keys[2], (PHYSFS_uint8) ((keys[1] >> 24) & 0xFF)); +} /* zip_update_crypto_keys */ + +static PHYSFS_uint8 zip_decrypt_byte(const PHYSFS_uint32 *keys) +{ + const PHYSFS_uint16 tmp = keys[2] | 2; + return (PHYSFS_uint8) ((tmp * (tmp ^ 1)) >> 8); +} /* zip_decrypt_byte */ + +static PHYSFS_sint64 zip_read_decrypt(ZIPfileinfo *finfo, void *buf, PHYSFS_uint64 len) +{ + PHYSFS_Io *io = finfo->io; + const PHYSFS_sint64 br = io->read(io, buf, len); + + /* Decompression the new data if necessary. */ + if (zip_entry_is_tradional_crypto(finfo->entry) && (br > 0)) + { + PHYSFS_uint32 *keys = finfo->crypto_keys; + PHYSFS_uint8 *ptr = (PHYSFS_uint8 *) buf; + PHYSFS_sint64 i; + for (i = 0; i < br; i++, ptr++) + { + const PHYSFS_uint8 ch = *ptr ^ zip_decrypt_byte(keys); + zip_update_crypto_keys(keys, ch); + *ptr = ch; + } /* for */ + } /* if */ + + return br; +} /* zip_read_decrypt */ + +static int zip_prep_crypto_keys(ZIPfileinfo *finfo, const PHYSFS_uint8 *crypto_header, const PHYSFS_uint8 *password) +{ + /* It doesn't appear to be documented in PKWare's APPNOTE.TXT, but you + need to use a different byte in the header to verify the password + if general purpose bit 3 is set. Discovered this from Info-Zip. + That's what the (verifier) value is doing, below. */ + + PHYSFS_uint32 *keys = finfo->crypto_keys; + const ZIPentry *entry = finfo->entry; + const int usedate = zip_entry_ignore_local_header(entry); + const PHYSFS_uint8 verifier = (PHYSFS_uint8) ((usedate ? (entry->dos_mod_time >> 8) : (entry->crc >> 24)) & 0xFF); + PHYSFS_uint8 finalbyte = 0; + int i = 0; + + /* initialize vector with defaults, then password, then header. */ + keys[0] = 305419896; + keys[1] = 591751049; + keys[2] = 878082192; + + while (*password) + zip_update_crypto_keys(keys, *(password++)); + + for (i = 0; i < 12; i++) + { + const PHYSFS_uint8 c = crypto_header[i] ^ zip_decrypt_byte(keys); + zip_update_crypto_keys(keys, c); + finalbyte = c; + } /* for */ + + /* you have a 1/256 chance of passing this test incorrectly. :/ */ + if (finalbyte != verifier) + BAIL_MACRO(PHYSFS_ERR_BAD_PASSWORD, 0); + + /* save the initial vector for seeking purposes. Not secure!! */ + memcpy(finfo->initial_crypto_keys, finfo->crypto_keys, 12); + return 1; +} /* zip_prep_crypto_keys */ + /* * Bridge physfs allocation functions to zlib's format... @@ -163,10 +265,17 @@ */ static int zlib_err(const int rc) { - __PHYSFS_setError(zlib_error_code(rc)); + PHYSFS_setErrorCode(zlib_error_code(rc)); return rc; } /* zlib_err */ +/* + * Hash a string for lookup an a ZIPinfo hashtable. + */ +static inline PHYSFS_uint32 zip_hash_string(const ZIPinfo *info, const char *s) +{ + return __PHYSFS_hashString(s, strlen(s)) % info->hashBuckets; +} /* zip_hash_string */ /* * Read an unsigned 64-bit int and swap to native byte order. @@ -206,7 +315,6 @@ static PHYSFS_sint64 ZIP_read(PHYSFS_Io *_io, void *buf, PHYSFS_uint64 len) { ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque; - PHYSFS_Io *io = finfo->io; ZIPentry *entry = finfo->entry; PHYSFS_sint64 retval = 0; PHYSFS_sint64 maxread = (PHYSFS_sint64) len; @@ -219,7 +327,7 @@ BAIL_IF_MACRO(maxread == 0, ERRPASS, 0); /* quick rejection. */ if (entry->compression_method == COMPMETH_NONE) - retval = io->read(io, buf, maxread); + retval = zip_read_decrypt(finfo, buf, maxread); else { finfo->stream.next_out = buf; @@ -240,7 +348,7 @@ if (br > ZIP_READBUFSIZE) br = ZIP_READBUFSIZE; - br = io->read(io, finfo->buffer, (PHYSFS_uint64) br); + br = zip_read_decrypt(finfo, finfo->buffer, (PHYSFS_uint64) br); if (br <= 0) break; @@ -282,12 +390,13 @@ ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque; ZIPentry *entry = finfo->entry; PHYSFS_Io *io = finfo->io; + const int encrypted = zip_entry_is_tradional_crypto(entry); BAIL_IF_MACRO(offset > entry->uncompressed_size, PHYSFS_ERR_PAST_EOF, 0); - if (entry->compression_method == COMPMETH_NONE) + if (!encrypted && (entry->compression_method == COMPMETH_NONE)) { - const PHYSFS_sint64 newpos = offset + entry->offset; + PHYSFS_sint64 newpos = offset + entry->offset; BAIL_IF_MACRO(!io->seek(io, newpos), ERRPASS, 0); finfo->uncompressed_position = (PHYSFS_uint32) offset; } /* if */ @@ -308,13 +417,16 @@ if (zlib_err(inflateInit2(&str, -MAX_WBITS)) != Z_OK) return 0; - if (!io->seek(io, entry->offset)) + if (!io->seek(io, entry->offset + (encrypted ? 12 : 0))) return 0; inflateEnd(&finfo->stream); inflateCopy(&finfo->stream, &str); inflateEnd(&str); finfo->uncompressed_position = finfo->compressed_position = 0; + + if (encrypted) + memcpy(finfo->crypto_keys, finfo->initial_crypto_keys, 12); } /* if */ while (finfo->uncompressed_position != offset) @@ -435,7 +547,7 @@ int found = 0; filelen = io->length(io); - BAIL_IF_MACRO(filelen == -1, ERRPASS, 0); + BAIL_IF_MACRO(filelen == -1, ERRPASS, -1); /* * Jump to the end of the file and start reading backwards. @@ -489,7 +601,7 @@ (buf[i + 3] == 0x06) ) { found = 1; /* that's the signature! */ - break; + break; } /* if */ } /* for */ @@ -537,71 +649,35 @@ } /* isZip */ -static void zip_free_entries(ZIPentry *entries, PHYSFS_uint64 max) +/* Find the ZIPentry for a path in platform-independent notation. */ +static ZIPentry *zip_find_entry(ZIPinfo *info, const char *path) { - PHYSFS_uint64 i; - for (i = 0; i < max; i++) + PHYSFS_uint32 hashval; + ZIPentry *prev = NULL; + ZIPentry *retval; + + if (*path == '\0') + return &info->root; + + hashval = zip_hash_string(info, path); + for (retval = info->hash[hashval]; retval; retval = retval->hashnext) { - ZIPentry *entry = &entries[i]; - if (entry->name != NULL) - allocator.Free(entry->name); + if (strcmp(retval->name, path) == 0) + { + if (prev != NULL) /* move this to the front of the list */ + { + prev->hashnext = retval->hashnext; + retval->hashnext = info->hash[hashval]; + info->hash[hashval] = retval; + } /* if */ + + return retval; + } /* if */ + + prev = retval; } /* for */ - allocator.Free(entries); -} /* zip_free_entries */ - - -/* - * This will find the ZIPentry associated with a path in platform-independent - * notation. Directories don't have ZIPentries associated with them, but - * (*isDir) will be set to non-zero if a dir was hit. - */ -static ZIPentry *zip_find_entry(const ZIPinfo *info, const char *path, - int *isDir) -{ - ZIPentry *a = info->entries; - PHYSFS_sint32 pathlen = (PHYSFS_sint32) strlen(path); - PHYSFS_sint64 lo = 0; - PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1); - PHYSFS_sint64 middle; - const char *thispath = NULL; - int rc; - - while (lo <= hi) - { - middle = lo + ((hi - lo) / 2); - thispath = a[middle].name; - rc = strncmp(path, thispath, pathlen); - - if (rc > 0) - lo = middle + 1; - - else if (rc < 0) - hi = middle - 1; - - else /* substring match...might be dir or entry or nothing. */ - { - if (isDir != NULL) - { - *isDir = (thispath[pathlen] == '/'); - if (*isDir) - return NULL; - } /* if */ - - if (thispath[pathlen] == '\0') /* found entry? */ - return &a[middle]; - /* adjust search params, try again. */ - else if (thispath[pathlen] > '/') - hi = middle - 1; - else - lo = middle + 1; - } /* if */ - } /* while */ - - if (isDir != NULL) - *isDir = 0; - - BAIL_MACRO(PHYSFS_ERR_NO_SUCH_PATH, NULL); + BAIL_MACRO(PHYSFS_ERR_NOT_FOUND, NULL); } /* zip_find_entry */ @@ -693,7 +769,7 @@ ZIPentry *entry; zip_expand_symlink_path(path); - entry = zip_find_entry(info, path, NULL); + entry = zip_find_entry(info, path); if (entry != NULL) { if (!zip_resolve(io, info, entry)) /* recursive! */ @@ -725,7 +801,7 @@ path = (char *) __PHYSFS_smallAlloc(size + 1); BAIL_IF_MACRO(!path, PHYSFS_ERR_OUT_OF_MEMORY, 0); - + if (entry->compression_method == COMPMETH_NONE) rc = __PHYSFS_readAll(io, path, size); @@ -788,6 +864,10 @@ * possible that's a Zip64 thing. */ + /* !!! FIXME: apparently these are zero if general purpose bit 3 is set, + !!! FIXME: which is probably true for Jar files, fwiw, but we don't + !!! FIXME: care about these values anyhow. */ + BAIL_IF_MACRO(!io->seek(io, entry->offset), ERRPASS, 0); BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); BAIL_IF_MACRO(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0); @@ -819,7 +899,10 @@ static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry) { int retval = 1; - ZipResolveType resolve_type = entry->resolved; + const ZipResolveType resolve_type = entry->resolved; + + if (resolve_type == ZIP_DIRECTORY) + return 1; /* we're good. */ /* Don't bother if we've failed to resolve this entry before. */ BAIL_IF_MACRO(resolve_type == ZIP_BROKEN_FILE, PHYSFS_ERR_CORRUPT, 0); @@ -861,6 +944,77 @@ } /* zip_resolve */ +static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry); + +/* Fill in missing parent directories. */ +static ZIPentry *zip_hash_ancestors(ZIPinfo *info, char *name) +{ + ZIPentry *retval = &info->root; + char *sep = strrchr(name, '/'); + + if (sep) + { + const size_t namelen = (sep - name) + 1; + + *sep = '\0'; /* chop off last piece. */ + retval = zip_find_entry(info, name); + *sep = '/'; + + if (retval != NULL) + { + if (retval->resolved != ZIP_DIRECTORY) + BAIL_MACRO(PHYSFS_ERR_CORRUPT, NULL); + return retval; /* already hashed. */ + } /* if */ + + /* okay, this is a new dir. Build and hash us. */ + retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + namelen); + BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memset(retval, '\0', sizeof (*retval)); + retval->name = ((char *) retval) + sizeof (ZIPentry); + memcpy(retval->name, name, namelen); + retval->name[namelen] = '\0'; + retval->resolved = ZIP_DIRECTORY; + if (!zip_hash_entry(info, retval)) + { + allocator.Free(retval); + return NULL; + } /* if */ + } /* else */ + + return retval; +} /* zip_hash_ancestors */ + + +static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry) +{ + PHYSFS_uint32 hashval; + ZIPentry *parent; + + assert(!zip_find_entry(info, entry->name)); /* checked elsewhere */ + + parent = zip_hash_ancestors(info, entry->name); + if (!parent) + return 0; + + hashval = zip_hash_string(info, entry->name); + entry->hashnext = info->hash[hashval]; + info->hash[hashval] = entry; + + entry->sibling = parent->children; + parent->children = entry; + return 1; +} /* zip_hash_entry */ + + +static int zip_entry_is_symlink(const ZIPentry *entry) +{ + return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) || + (entry->resolved == ZIP_BROKEN_SYMLINK) || + (entry->symlink)); +} /* zip_entry_is_symlink */ + + static int zip_version_does_symlinks(PHYSFS_uint32 version) { int retval = 0; @@ -893,14 +1047,6 @@ } /* zip_version_does_symlinks */ -static int zip_entry_is_symlink(const 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); @@ -936,9 +1082,11 @@ } /* zip_dos_time_to_physfs_time */ -static int zip_load_entry(PHYSFS_Io *io, const int zip64, ZIPentry *entry, - PHYSFS_uint64 ofs_fixup) +static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64, + const PHYSFS_uint64 ofs_fixup) { + ZIPentry entry; + ZIPentry *retval = NULL; PHYSFS_uint16 fnamelen, extralen, commentlen; PHYSFS_uint32 external_attr; PHYSFS_uint32 starting_disk; @@ -947,43 +1095,57 @@ PHYSFS_uint32 ui32; PHYSFS_sint64 si64; + memset(&entry, '\0', sizeof (entry)); + /* sanity check with central directory signature... */ - BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); - BAIL_IF_MACRO(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0); + if (!readui32(io, &ui32)) return NULL; + BAIL_IF_MACRO(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL); /* Get the pertinent parts of the record... */ - BAIL_IF_MACRO(!readui16(io, &entry->version), ERRPASS, 0); - BAIL_IF_MACRO(!readui16(io, &entry->version_needed), ERRPASS, 0); - BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0); /* general bits */ - BAIL_IF_MACRO(!readui16(io, &entry->compression_method), ERRPASS, 0); - BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); - entry->last_mod_time = zip_dos_time_to_physfs_time(ui32); - BAIL_IF_MACRO(!readui32(io, &entry->crc), ERRPASS, 0); - BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); - entry->compressed_size = (PHYSFS_uint64) ui32; - BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); - entry->uncompressed_size = (PHYSFS_uint64) ui32; - BAIL_IF_MACRO(!readui16(io, &fnamelen), ERRPASS, 0); - BAIL_IF_MACRO(!readui16(io, &extralen), ERRPASS, 0); - BAIL_IF_MACRO(!readui16(io, &commentlen), ERRPASS, 0); - BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0); + if (!readui16(io, &entry.version)) return NULL; + if (!readui16(io, &entry.version_needed)) return NULL; + if (!readui16(io, &entry.general_bits)) return NULL; /* general bits */ + if (!readui16(io, &entry.compression_method)) return NULL; + if (!readui32(io, &entry.dos_mod_time)) return NULL; + entry.last_mod_time = zip_dos_time_to_physfs_time(entry.dos_mod_time); + if (!readui32(io, &entry.crc)) return NULL; + if (!readui32(io, &ui32)) return NULL; + entry.compressed_size = (PHYSFS_uint64) ui32; + if (!readui32(io, &ui32)) return NULL; + entry.uncompressed_size = (PHYSFS_uint64) ui32; + if (!readui16(io, &fnamelen)) return NULL; + if (!readui16(io, &extralen)) return NULL; + if (!readui16(io, &commentlen)) return NULL; + if (!readui16(io, &ui16)) return NULL; starting_disk = (PHYSFS_uint32) ui16; - BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0); /* internal file attribs */ - BAIL_IF_MACRO(!readui32(io, &external_attr), ERRPASS, 0); - BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); + if (!readui16(io, &ui16)) return NULL; /* internal file attribs */ + if (!readui32(io, &external_attr)) return NULL; + if (!readui32(io, &ui32)) return NULL; offset = (PHYSFS_uint64) ui32; - entry->symlink = NULL; /* will be resolved later, if necessary. */ - entry->resolved = (zip_has_symlink_attr(entry, external_attr)) ? - ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE; + retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + fnamelen + 1); + BAIL_IF_MACRO(retval == NULL, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memcpy(retval, &entry, sizeof (*retval)); + retval->name = ((char *) retval) + sizeof (ZIPentry); - entry->name = (char *) allocator.Malloc(fnamelen + 1); - BAIL_IF_MACRO(entry->name == NULL, PHYSFS_ERR_OUT_OF_MEMORY, 0); - if (!__PHYSFS_readAll(io, entry->name, fnamelen)) + if (!__PHYSFS_readAll(io, retval->name, fnamelen)) goto zip_load_entry_puked; - entry->name[fnamelen] = '\0'; /* null-terminate the filename. */ - zip_convert_dos_path(entry, entry->name); + retval->name[fnamelen] = '\0'; /* null-terminate the filename. */ + zip_convert_dos_path(retval, retval->name); + + retval->symlink = NULL; /* will be resolved later, if necessary. */ + + if (retval->name[fnamelen - 1] == '/') + { + retval->name[fnamelen - 1] = '\0'; + retval->resolved = ZIP_DIRECTORY; + } /* if */ + else + { + retval->resolved = (zip_has_symlink_attr(&entry, external_attr)) ? + ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE; + } /* else */ si64 = io->tell(io); if (si64 == -1) @@ -996,8 +1158,8 @@ if ( (zip64) && ((offset == 0xFFFFFFFF) || (starting_disk == 0xFFFFFFFF) || - (entry->compressed_size == 0xFFFFFFFF) || - (entry->uncompressed_size == 0xFFFFFFFF)) ) + (retval->compressed_size == 0xFFFFFFFF) || + (retval->uncompressed_size == 0xFFFFFFFF)) ) { int found = 0; PHYSFS_uint16 sig, len; @@ -1023,18 +1185,18 @@ GOTO_IF_MACRO(!found, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (entry->uncompressed_size == 0xFFFFFFFF) + if (retval->uncompressed_size == 0xFFFFFFFF) { GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui64(io, &entry->uncompressed_size)) + if (!readui64(io, &retval->uncompressed_size)) goto zip_load_entry_puked; len -= 8; } /* if */ - if (entry->compressed_size == 0xFFFFFFFF) + if (retval->compressed_size == 0xFFFFFFFF) { GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - if (!readui64(io, &entry->compressed_size)) + if (!readui64(io, &retval->compressed_size)) goto zip_load_entry_puked; len -= 8; } /* if */ @@ -1060,69 +1222,74 @@ GOTO_IF_MACRO(starting_disk != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked); - entry->offset = offset + ofs_fixup; + retval->offset = offset + ofs_fixup; /* seek to the start of the next entry in the central directory... */ if (!io->seek(io, si64 + extralen + commentlen)) goto zip_load_entry_puked; - return 1; /* success. */ + return retval; /* success. */ zip_load_entry_puked: - allocator.Free(entry->name); - return 0; /* failure. */ + allocator.Free(retval); + return NULL; /* failure. */ } /* zip_load_entry */ -static int zip_entry_cmp(void *_a, size_t one, size_t two) -{ - if (one != two) - { - const ZIPentry *a = (const ZIPentry *) _a; - return strcmp(a[one].name, a[two].name); - } /* if */ - - return 0; -} /* zip_entry_cmp */ - - -static void zip_entry_swap(void *_a, size_t one, size_t two) +/* This leaves things allocated on error; the caller will clean up the mess. */ +static int zip_load_entries(ZIPinfo *info, + const PHYSFS_uint64 data_ofs, + const PHYSFS_uint64 central_ofs, + const PHYSFS_uint64 entry_count) { - if (one != two) - { - ZIPentry tmp; - ZIPentry *first = &(((ZIPentry *) _a)[one]); - ZIPentry *second = &(((ZIPentry *) _a)[two]); - memcpy(&tmp, first, sizeof (ZIPentry)); - memcpy(first, second, sizeof (ZIPentry)); - memcpy(second, &tmp, sizeof (ZIPentry)); - } /* if */ -} /* zip_entry_swap */ - - -static int zip_load_entries(PHYSFS_Io *io, ZIPinfo *info, - const PHYSFS_uint64 data_ofs, - const PHYSFS_uint64 central_ofs) -{ - const PHYSFS_uint64 max = info->entryCount; + PHYSFS_Io *io = info->io; const int zip64 = info->zip64; PHYSFS_uint64 i; - BAIL_IF_MACRO(!io->seek(io, central_ofs), ERRPASS, 0); + if (!io->seek(io, central_ofs)) + return 0; - info->entries = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) * max); - BAIL_IF_MACRO(!info->entries, PHYSFS_ERR_OUT_OF_MEMORY, 0); + for (i = 0; i < entry_count; i++) + { + ZIPentry *entry = zip_load_entry(io, zip64, data_ofs); + ZIPentry *find; + + if (!entry) + return 0; - for (i = 0; i < max; i++) - { - if (!zip_load_entry(io, zip64, &info->entries[i], data_ofs)) + find = zip_find_entry(info, entry->name); + if (find != NULL) /* duplicate? */ { - zip_free_entries(info->entries, i); + if (find->last_mod_time != 0) /* duplicate? */ + { + allocator.Free(entry); + BAIL_MACRO(PHYSFS_ERR_CORRUPT, 0); + } /* if */ + else /* we filled this in as a placeholder. Update it. */ + { + find->offset = entry->offset; + find->version = entry->version; + find->version_needed = entry->version_needed; + find->compression_method = entry->compression_method; + find->crc = entry->crc; + find->compressed_size = entry->compressed_size; + find->uncompressed_size = entry->uncompressed_size; + find->last_mod_time = entry->last_mod_time; + allocator.Free(entry); + continue; + } /* else */ + } /* if */ + + if (!zip_hash_entry(info, entry)) + { + allocator.Free(entry); return 0; } /* if */ + + if (zip_entry_is_tradional_crypto(entry)) + info->has_crypto = 1; } /* for */ - __PHYSFS_sort(info->entries, (size_t) max, zip_entry_cmp, zip_entry_swap); return 1; } /* zip_load_entries */ @@ -1182,36 +1349,47 @@ /* Just try moving back at most 256k. Oh well. */ if ((offset < pos) && (pos > 4)) { - /* we assume you can eat this stack if you handle Zip64 files. */ - PHYSFS_uint8 buf[256 * 1024]; + const PHYSFS_uint64 maxbuflen = 256 * 1024; PHYSFS_uint64 len = pos - offset; + PHYSFS_uint8 *buf = NULL; PHYSFS_sint32 i; - if (len > sizeof (buf)) - len = sizeof (buf); + if (len > maxbuflen) + len = maxbuflen; + + buf = (PHYSFS_uint8 *) __PHYSFS_smallAlloc(len); + BAIL_IF_MACRO(!buf, PHYSFS_ERR_OUT_OF_MEMORY, -1); - BAIL_IF_MACRO(!io->seek(io, pos - len), ERRPASS, -1); - BAIL_IF_MACRO(!__PHYSFS_readAll(io, buf, len), ERRPASS, -1); + if (!io->seek(io, pos - len) || !__PHYSFS_readAll(io, buf, len)) + { + __PHYSFS_smallFree(buf); + return -1; /* error was set elsewhere. */ + } /* if */ + for (i = (PHYSFS_sint32) (len - 4); i >= 0; i--) { - if (buf[i] != 0x50) - continue; - if ( (buf[i+1] == 0x4b) && - (buf[i+2] == 0x06) && - (buf[i+3] == 0x06) ) + if ( (buf[i] == 0x50) && (buf[i+1] == 0x4b) && + (buf[i+2] == 0x06) && (buf[i+3] == 0x06) ) + { + __PHYSFS_smallFree(buf); return pos - (len - i); + } /* if */ } /* for */ + + __PHYSFS_smallFree(buf); } /* if */ BAIL_MACRO(PHYSFS_ERR_CORRUPT, -1); /* didn't find it. */ } /* zip64_find_end_of_central_dir */ -static int zip64_parse_end_of_central_dir(PHYSFS_Io *io, ZIPinfo *info, +static int zip64_parse_end_of_central_dir(ZIPinfo *info, PHYSFS_uint64 *data_start, PHYSFS_uint64 *dir_ofs, + PHYSFS_uint64 *entry_count, PHYSFS_sint64 pos) { + PHYSFS_Io *io = info->io; PHYSFS_uint64 ui64; PHYSFS_uint32 ui32; PHYSFS_uint16 ui16; @@ -1279,8 +1457,8 @@ BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0); /* total number of entries in the central dir */ - BAIL_IF_MACRO(!readui64(io, &info->entryCount), ERRPASS, 0); - BAIL_IF_MACRO(ui64 != info->entryCount, PHYSFS_ERR_CORRUPT, 0); + BAIL_IF_MACRO(!readui64(io, entry_count), ERRPASS, 0); + BAIL_IF_MACRO(ui64 != *entry_count, PHYSFS_ERR_CORRUPT, 0); /* size of the central directory */ BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0); @@ -1300,10 +1478,12 @@ } /* zip64_parse_end_of_central_dir */ -static int zip_parse_end_of_central_dir(PHYSFS_Io *io, ZIPinfo *info, +static int zip_parse_end_of_central_dir(ZIPinfo *info, PHYSFS_uint64 *data_start, - PHYSFS_uint64 *dir_ofs) + PHYSFS_uint64 *dir_ofs, + PHYSFS_uint64 *entry_count) { + PHYSFS_Io *io = info->io; PHYSFS_uint16 entryCount16; PHYSFS_uint32 offset32; PHYSFS_uint32 ui32; @@ -1323,10 +1503,12 @@ /* Seek back to see if "Zip64 end of central directory locator" exists. */ /* this record is 20 bytes before end-of-central-dir */ - rc = zip64_parse_end_of_central_dir(io, info, data_start, dir_ofs, pos-20); - BAIL_IF_MACRO(rc == 0, ERRPASS, 0); - if (rc == 1) - return 1; /* we're done here. */ + rc = zip64_parse_end_of_central_dir(info, data_start, dir_ofs, + entry_count, pos - 20); + + /* Error or success? Bounce out of here. Keep going if not zip64. */ + if ((rc == 0) || (rc == 1)) + return rc; assert(rc == -1); /* no error, just not a Zip64 archive. */ @@ -1348,7 +1530,7 @@ BAIL_IF_MACRO(!readui16(io, &entryCount16), ERRPASS, 0); BAIL_IF_MACRO(ui16 != entryCount16, PHYSFS_ERR_CORRUPT, 0); - info->entryCount = entryCount16; + *entry_count = entryCount16; /* size of the central directory */ BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0); @@ -1385,11 +1567,30 @@ } /* zip_parse_end_of_central_dir */ +static int zip_alloc_hashtable(ZIPinfo *info, const PHYSFS_uint64 entry_count) +{ + size_t alloclen; + + info->hashBuckets = (size_t) (entry_count / 5); + if (!info->hashBuckets) + info->hashBuckets = 1; + + alloclen = info->hashBuckets * sizeof (ZIPentry *); + info->hash = (ZIPentry **) allocator.Malloc(alloclen); + BAIL_IF_MACRO(!info->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0); + memset(info->hash, '\0', alloclen); + + return 1; +} /* zip_alloc_hashtable */ + +static void ZIP_closeArchive(void *opaque); + static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, int forWriting) { ZIPinfo *info = NULL; - PHYSFS_uint64 data_start; - PHYSFS_uint64 cent_dir_ofs; + PHYSFS_uint64 dstart; /* data start */ + PHYSFS_uint64 cdir_ofs; /* central dir offset */ + PHYSFS_uint64 entry_count; assert(io != NULL); /* shouldn't ever happen. */ @@ -1399,138 +1600,40 @@ info = (ZIPinfo *) allocator.Malloc(sizeof (ZIPinfo)); BAIL_IF_MACRO(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL); memset(info, '\0', sizeof (ZIPinfo)); + info->root.resolved = ZIP_DIRECTORY; info->io = io; - if (!zip_parse_end_of_central_dir(io, info, &data_start, ¢_dir_ofs)) + if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &entry_count)) + goto ZIP_openarchive_failed; + else if (!zip_alloc_hashtable(info, entry_count)) + goto ZIP_openarchive_failed; + else if (!zip_load_entries(info, dstart, cdir_ofs, entry_count)) goto ZIP_openarchive_failed; - if (!zip_load_entries(io, info, data_start, cent_dir_ofs)) - goto ZIP_openarchive_failed; - + assert(info->root.sibling == NULL); return info; ZIP_openarchive_failed: - if (info != NULL) - allocator.Free(info); - + info->io = NULL; /* don't let ZIP_closeArchive destroy (io). */ + ZIP_closeArchive(info); return NULL; } /* ZIP_openArchive */ -static PHYSFS_sint64 zip_find_start_of_dir(ZIPinfo *info, const char *path, - int stop_on_first_find) -{ - PHYSFS_sint64 lo = 0; - PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1); - PHYSFS_sint64 middle; - PHYSFS_uint32 dlen = (PHYSFS_uint32) strlen(path); - PHYSFS_sint64 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 (stop_on_first_find) /* Just checking dir's existance? */ - return middle; - - if (name[dlen + 1] == '\0') /* Skip initial dir entry. */ - return (middle + 1); - - /* 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 */ - - -/* - * Moved to seperate function so we can use alloca then immediately throw - * away the allocated stack space... - */ -static void doEnumCallback(PHYSFS_EnumFilesCallback cb, void *callbackdata, - const char *odir, const char *str, PHYSFS_sint32 ln) -{ - char *newstr = __PHYSFS_smallAlloc(ln + 1); - if (newstr == NULL) - return; - - memcpy(newstr, str, ln); - newstr[ln] = '\0'; - cb(callbackdata, odir, newstr); - __PHYSFS_smallFree(newstr); -} /* doEnumCallback */ - - -static void ZIP_enumerateFiles(PHYSFS_Dir *opaque, const char *dname, - int omitSymLinks, PHYSFS_EnumFilesCallback cb, +static void ZIP_enumerateFiles(void *opaque, const char *dname, + PHYSFS_EnumFilesCallback cb, const char *origdir, void *callbackdata) { ZIPinfo *info = ((ZIPinfo *) opaque); - PHYSFS_sint32 dlen, dlen_inc; - PHYSFS_sint64 i, max; - - i = zip_find_start_of_dir(info, dname, 0); - if (i == -1) /* no such directory. */ - return; - - dlen = (PHYSFS_sint32) strlen(dname); - if ((dlen > 0) && (dname[dlen - 1] == '/')) /* ignore trailing slash. */ - dlen--; - - dlen_inc = ((dlen > 0) ? 1 : 0) + dlen; - max = (PHYSFS_sint64) info->entryCount; - while (i < max) + const ZIPentry *entry = zip_find_entry(info, dname); + if (entry && (entry->resolved == ZIP_DIRECTORY)) { - char *e = info->entries[i].name; - if ((dlen) && ((strncmp(e, dname, dlen) != 0) || (e[dlen] != '/'))) - break; /* past end of this dir; we're done. */ - - if ((omitSymLinks) && (zip_entry_is_symlink(&info->entries[i]))) - i++; - else + for (entry = entry->children; entry; entry = entry->sibling) { - char *add = e + dlen_inc; - char *ptr = strchr(add, '/'); - PHYSFS_sint32 ln = (PHYSFS_sint32) ((ptr) ? ptr-add : strlen(add)); - doEnumCallback(cb, callbackdata, origdir, add, ln); - ln += dlen_inc; /* point past entry to children... */ - - /* increment counter and skip children of subdirs... */ - while ((++i < max) && (ptr != NULL)) - { - char *e_new = info->entries[i].name; - if ((strncmp(e, e_new, ln) != 0) || (e_new[ln] != '/')) - break; - } /* while */ - } /* else */ - } /* while */ + const char *ptr = strrchr(entry->name, '/'); + cb(callbackdata, origdir, ptr ? ptr + 1 : entry->name); + } /* for */ + } /* if */ } /* ZIP_enumerateFiles */ @@ -1561,15 +1664,33 @@ } /* zip_get_io */ -static PHYSFS_Io *ZIP_openRead(PHYSFS_Dir *opaque, const char *fnm, - int *fileExists) +static PHYSFS_Io *ZIP_openRead(void *opaque, const char *filename) { PHYSFS_Io *retval = NULL; ZIPinfo *info = (ZIPinfo *) opaque; - ZIPentry *entry = zip_find_entry(info, fnm, NULL); + ZIPentry *entry = zip_find_entry(info, filename); ZIPfileinfo *finfo = NULL; + PHYSFS_Io *io = NULL; + PHYSFS_uint8 *password = NULL; + int i; - *fileExists = (entry != NULL); + /* if not found, see if maybe "$PASSWORD" is appended. */ + if ((!entry) && (info->has_crypto)) + { + const char *ptr = strrchr(filename, '$'); + if (ptr != NULL) + { + const PHYSFS_uint64 len = (PHYSFS_uint64) (ptr - filename); + char *str = (char *) __PHYSFS_smallAlloc(len + 1); + BAIL_IF_MACRO(!str, PHYSFS_ERR_OUT_OF_MEMORY, NULL); + memcpy(str, filename, len); + str[len] = '\0'; + entry = zip_find_entry(info, str); + __PHYSFS_smallFree(str); + password = (PHYSFS_uint8 *) (ptr + 1); + } /* if */ + } /* if */ + BAIL_IF_MACRO(!entry, ERRPASS, NULL); retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io)); @@ -1579,8 +1700,9 @@ GOTO_IF_MACRO(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed); memset(finfo, '\0', sizeof (ZIPfileinfo)); - finfo->io = zip_get_io(info->io, info, entry); - GOTO_IF_MACRO(!finfo->io, ERRPASS, ZIP_openRead_failed); + io = zip_get_io(info->io, info, entry); + GOTO_IF_MACRO(!io, ERRPASS, ZIP_openRead_failed); + finfo->io = io; finfo->entry = ((entry->symlink != NULL) ? entry->symlink : entry); initializeZStream(&finfo->stream); @@ -1593,6 +1715,18 @@ goto ZIP_openRead_failed; } /* if */ + if (!zip_entry_is_tradional_crypto(entry)) + GOTO_IF_MACRO(password != NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed); + else + { + PHYSFS_uint8 crypto_header[12]; + GOTO_IF_MACRO(password == NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed); + if (io->read(io, crypto_header, 12) != 12) + goto ZIP_openRead_failed; + else if (!zip_prep_crypto_keys(finfo, crypto_header, password)) + goto ZIP_openRead_failed; + } /* if */ + memcpy(retval, &ZIP_Io, sizeof (PHYSFS_Io)); retval->opaque = finfo; @@ -1620,53 +1754,74 @@ } /* ZIP_openRead */ -static PHYSFS_Io *ZIP_openWrite(PHYSFS_Dir *opaque, const char *filename) +static PHYSFS_Io *ZIP_openWrite(void *opaque, const char *filename) { BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL); } /* ZIP_openWrite */ -static PHYSFS_Io *ZIP_openAppend(PHYSFS_Dir *opaque, const char *filename) +static PHYSFS_Io *ZIP_openAppend(void *opaque, const char *filename) { BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL); } /* ZIP_openAppend */ -static void ZIP_closeArchive(PHYSFS_Dir *opaque) +static void ZIP_closeArchive(void *opaque) { - ZIPinfo *zi = (ZIPinfo *) (opaque); - zi->io->destroy(zi->io); - zip_free_entries(zi->entries, zi->entryCount); - allocator.Free(zi); + ZIPinfo *info = (ZIPinfo *) (opaque); + + if (!info) + return; + + if (info->io) + info->io->destroy(info->io); + + assert(info->root.sibling == NULL); + assert(info->hash || (info->root.children == NULL)); + + if (info->hash) + { + size_t i; + for (i = 0; i < info->hashBuckets; i++) + { + ZIPentry *entry; + ZIPentry *next; + for (entry = info->hash[i]; entry; entry = next) + { + next = entry->hashnext; + allocator.Free(entry); + } /* for */ + } /* for */ + allocator.Free(info->hash); + } /* if */ + + allocator.Free(info); } /* ZIP_closeArchive */ -static int ZIP_remove(PHYSFS_Dir *opaque, const char *name) +static int ZIP_remove(void *opaque, const char *name) { BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0); } /* ZIP_remove */ -static int ZIP_mkdir(PHYSFS_Dir *opaque, const char *name) +static int ZIP_mkdir(void *opaque, const char *name) { BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0); } /* ZIP_mkdir */ -static int ZIP_stat(PHYSFS_Dir *opaque, const char *filename, int *exists, - PHYSFS_Stat *stat) +static int ZIP_stat(void *opaque, const char *filename, PHYSFS_Stat *stat) { - int isDir = 0; - const ZIPinfo *info = (const ZIPinfo *) opaque; - const ZIPentry *entry = zip_find_entry(info, filename, &isDir); + ZIPinfo *info = (ZIPinfo *) opaque; + const ZIPentry *entry = zip_find_entry(info, filename); /* !!! FIXME: does this need to resolve entries here? */ - *exists = isDir || (entry != 0); - if (!*exists) + if (entry == NULL) return 0; - if (isDir) + else if (entry->resolved == ZIP_DIRECTORY) { stat->filesize = 0; stat->filetype = PHYSFS_FILETYPE_DIRECTORY; @@ -1695,24 +1850,26 @@ const PHYSFS_Archiver __PHYSFS_Archiver_ZIP = { + CURRENT_PHYSFS_ARCHIVER_API_VERSION, { "ZIP", "PkZip/WinZip/Info-Zip compatible", "Ryan C. Gordon ", - "http://icculus.org/physfs/", + "https://icculus.org/physfs/", + 1, /* supportsSymlinks */ }, - ZIP_openArchive, /* openArchive() method */ - ZIP_enumerateFiles, /* enumerateFiles() method */ - ZIP_openRead, /* openRead() method */ - ZIP_openWrite, /* openWrite() method */ - ZIP_openAppend, /* openAppend() method */ - ZIP_remove, /* remove() method */ - ZIP_mkdir, /* mkdir() method */ - ZIP_closeArchive, /* closeArchive() method */ - ZIP_stat /* stat() method */ + ZIP_openArchive, + ZIP_enumerateFiles, + ZIP_openRead, + ZIP_openWrite, + ZIP_openAppend, + ZIP_remove, + ZIP_mkdir, + ZIP_stat, + ZIP_closeArchive }; #endif /* defined PHYSFS_SUPPORTS_ZIP */ -/* end of zip.c ... */ +/* end of archiver_zip.c ... */