From 8aa5ad204a3bca4c499c463c9573c6dab98bf932 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 22 Jul 2024 00:08:04 +0200 Subject: [PATCH] Support zip archives not starting at zero offset (#346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support zip archives not starting at zero offset Zip archives are usually read from the back to the front, making it possible to append them to other files (e.g. executables) in order to provide some kind of embedded filesystem. This PR makes it possible to read, write, and update such zip files without affecting the existing functionality. * Use updated miniz version The new miniz version correctly computes the archive starting offset. * Fix compilation errors Use the correct mkdir signature on Windows vs other platforms. Remove CHMOD macro, it was simply wrapping the chmod libc call. * Address some fixes --------- Co-authored-by: Kuba Podgórski --- src/miniz.h | 288 +++++++++++++++++--------------------------- src/zip.c | 54 ++++++--- src/zip.h | 10 ++ test/CMakeLists.txt | 6 + test/test_offset.c | 96 +++++++++++++++ 5 files changed, 259 insertions(+), 195 deletions(-) create mode 100644 test/test_offset.c diff --git a/src/miniz.h b/src/miniz.h index cdacedea..94afbd19 100644 --- a/src/miniz.h +++ b/src/miniz.h @@ -1,7 +1,7 @@ #ifndef MINIZ_EXPORT #define MINIZ_EXPORT #endif -/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP +/* miniz.c 3.0.2 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: @@ -740,7 +740,9 @@ extern "C" { /* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly * slower, and raw/dynamic blocks will be output more frequently). */ +#ifndef TDEFL_LESS_MEMORY #define TDEFL_LESS_MEMORY 0 +#endif /* tdefl_init() compression flags logically OR'd together (low 12 bits contain * the max. number of probes per dictionary search): */ @@ -1424,11 +1426,6 @@ MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); -MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2_rpb(mz_zip_archive *pZip, - const char *pFilename, - mz_uint flags, - mz_uint64 file_start_ofs, - mz_uint64 archive_size); /* Read an archive from an already opened FILE, beginning at the current file * position. */ @@ -1694,8 +1691,6 @@ MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); -MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2_noreopen( - mz_zip_archive *pZip, const char *pFilename, mz_uint flags); /* Adds the contents of a memory buffer to an archive. These functions record * the current local time into the archive. */ @@ -1832,6 +1827,8 @@ MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2( #endif #endif /* MINIZ_NO_ARCHIVE_APIS */ + +#ifndef MINIZ_HEADER_FILE_ONLY /************************************************************************** * * Copyright 2013-2014 RAD Game Tools and Valve Software @@ -2144,8 +2141,7 @@ mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { } int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, - const unsigned char *pSource, mz_ulong pSource_len, - int level) { + const unsigned char *pSource, mz_ulong source_len, int level) { int status; mz_stream stream; memset(&stream, 0, sizeof(stream)); @@ -2153,12 +2149,11 @@ int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, #if defined(__MINGW32__) || defined(__MINGW64__) || defined(__WATCOMC__) /* In case mz_ulong is 64-bits (argh I hate longs). */ #else - if ((mz_uint64)(pSource_len | *pDest_len) > 0xFFFFFFFFU) + if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; #endif - stream.next_in = pSource; - stream.avail_in = (mz_uint32)pSource_len; + stream.avail_in = (mz_uint32)source_len; stream.next_out = pDest; stream.avail_out = (mz_uint32)*pDest_len; @@ -2408,7 +2403,6 @@ int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; #endif - stream.next_in = pSource; stream.avail_in = (mz_uint32)*pSource_len; stream.next_out = pDest; @@ -4952,9 +4946,17 @@ extern "C" { #else #include -#if defined(_MSC_VER) || defined(__MINGW64__) +#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__) +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif +#ifndef __cplusplus +#define MICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS 0 +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif #include static WCHAR *mz_utf8z_to_widechar(const char *str) { @@ -5000,12 +5002,21 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { #endif } +#if defined(__MINGW32__) +static int mz_stat(const char *path, struct _stat *buffer) { + WCHAR *wPath = mz_utf8z_to_widechar(path); + int res = _wstat(wPath, buffer); + free(wPath); + return res; +} +#else static int mz_stat64(const char *path, struct __stat64 *buffer) { WCHAR *wPath = mz_utf8z_to_widechar(path); int res = _wstat64(wPath, buffer); free(wPath); return res; } +#endif static int mz_mkdir(const char *pDirname) { WCHAR *wDirname = mz_utf8z_to_widechar(pDirname); @@ -5023,8 +5034,13 @@ static int mz_mkdir(const char *pDirname) { #define MZ_FWRITE fwrite #define MZ_FTELL64 _ftelli64 #define MZ_FSEEK64 _fseeki64 +#if defined(__MINGW32__) +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT mz_stat +#else #define MZ_FILE_STAT_STRUCT _stat64 #define MZ_FILE_STAT mz_stat64 +#endif #define MZ_FFLUSH fflush #define MZ_FREOPEN mz_freopen #define MZ_DELETE_FILE remove @@ -5085,7 +5101,8 @@ static int mz_mkdir(const char *pDirname) { #define MZ_DELETE_FILE remove #define MZ_MKDIR(d) mkdir(d, 0755) -#elif defined(__APPLE__) || defined(__FreeBSD__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || \ + (defined(__linux__) && defined(__x86_64__)) #ifndef MINIZ_NO_TIME #include #endif @@ -5600,7 +5617,7 @@ static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, /* Give up if we've searched the entire file, or we've gone back "too far" * (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= - (MZ_UINT16_MAX + record_size))) + ((mz_uint64)(MZ_UINT16_MAX) + record_size))) return MZ_FALSE; cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); @@ -5610,11 +5627,25 @@ static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, return MZ_TRUE; } +static mz_bool mz_zip_reader_eocd64_valid(mz_zip_archive *pZip, uint64_t offset, + uint8_t *buf) { + if (pZip->m_pRead(pZip->m_pIO_opaque, offset, buf, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(buf + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + return MZ_TRUE; + } + } + + return MZ_FALSE; +} + static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) { mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; - mz_uint64 cdir_ofs = 0; + mz_uint64 cdir_ofs = 0, eocd_ofs = 0, archive_ofs = 0; mz_int64 cur_file_ofs = 0; const mz_uint8 *p; @@ -5645,6 +5676,7 @@ static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + eocd_ofs = cur_file_ofs; /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != @@ -5664,25 +5696,38 @@ static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { - zip64_end_of_central_dir_ofs = MZ_READ_LE64( - pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); - if (zip64_end_of_central_dir_ofs > - (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - - if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, - pZip64_end_of_central_dir, - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { - if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { - pZip->m_pState->m_zip64 = MZ_TRUE; - } - } + pZip->m_pState->m_zip64 = MZ_TRUE; } } } + if (pZip->m_pState->m_zip64) { + /* Try locating the EOCD64 right before the EOCD64 locator. This works even + * when the effective start of the zip header is not yet known. */ + if (cur_file_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + zip64_end_of_central_dir_ofs = cur_file_ofs - + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE - + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir)) { + /* That failed, try reading where the locator tells us to. */ + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + } + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); @@ -5753,6 +5798,29 @@ static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + if (eocd_ofs < cdir_ofs + cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* The end of central dir follows the central dir, unless the zip file has + * some trailing data (e.g. it is appended to an executable file). */ + archive_ofs = eocd_ofs - (cdir_ofs + cdir_size); + if (pZip->m_pState->m_zip64) { + if (archive_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + archive_ofs -= MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Update the archive start position, but only if not specified. */ + if ((pZip->m_zip_type == MZ_ZIP_TYPE_FILE || + pZip->m_zip_type == MZ_ZIP_TYPE_CFILE) && + pZip->m_pState->m_file_archive_start_ofs == 0) { + pZip->m_pState->m_file_archive_start_ofs = archive_ofs; + pZip->m_archive_size -= archive_ofs; + } + pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { @@ -6101,60 +6169,6 @@ mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, return MZ_TRUE; } -mz_bool mz_zip_reader_init_file_v2_rpb(mz_zip_archive *pZip, - const char *pFilename, mz_uint flags, - mz_uint64 file_start_ofs, - mz_uint64 archive_size) { - mz_uint64 file_size; - MZ_FILE *pFile; - - if ((!pZip) || (!pFilename) || - ((archive_size) && - (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pFile = MZ_FOPEN(pFilename, "r+b"); - if (!pFile) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); - - file_size = archive_size; - if (!file_size) { - if (MZ_FSEEK64(pFile, 0, SEEK_END)) { - MZ_FCLOSE(pFile); - return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); - } - - file_size = MZ_FTELL64(pFile); - } - - /* TODO: Better sanity check archive_size and the # of actual remaining bytes - */ - - if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) { - MZ_FCLOSE(pFile); - return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); - } - - if (!mz_zip_reader_init_internal(pZip, flags)) { - MZ_FCLOSE(pFile); - return MZ_FALSE; - } - - pZip->m_zip_type = MZ_ZIP_TYPE_FILE; - pZip->m_pRead = mz_zip_file_read_func; - pZip->m_pIO_opaque = pZip; - pZip->m_pState->m_pFile = pFile; - pZip->m_archive_size = file_size; - pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; - - if (!mz_zip_reader_read_central_dir(pZip, flags)) { - mz_zip_reader_end_internal(pZip, MZ_FALSE); - return MZ_FALSE; - } - - return MZ_TRUE; -} - mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) { mz_uint64 cur_file_ofs; @@ -6627,7 +6641,7 @@ static mz_bool mz_zip_reader_extract_to_mem_no_alloc1( if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + cur_file_ofs += (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) @@ -6868,7 +6882,7 @@ mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + cur_file_ofs += (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) @@ -7115,7 +7129,7 @@ mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, } pState->cur_file_ofs += - MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + (mz_uint64)(MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > @@ -8183,88 +8197,6 @@ mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, return MZ_TRUE; } -mz_bool mz_zip_writer_init_from_reader_v2_noreopen(mz_zip_archive *pZip, - const char *pFilename, - mz_uint flags) { - mz_zip_internal_state *pState; - - if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) { - /* We don't support converting a non-zip64 file to zip64 - this seems like - * more trouble than it's worth. (What about the existing 32-bit data - * descriptors that could follow the compressed data?) */ - if (!pZip->m_pState->m_zip64) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - /* No sense in trying to write to an archive that's already at the support max - * size */ - if (pZip->m_pState->m_zip64) { - if (pZip->m_total_files == MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - } else { - if (pZip->m_total_files == MZ_UINT16_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); - - if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + - MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) - return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); - } - - pState = pZip->m_pState; - - if (pState->m_pFile) { -#ifdef MINIZ_NO_STDIO - (void)pFilename; - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); -#else - if (pZip->m_pIO_opaque != pZip) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) { - if (!pFilename) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - } - - pZip->m_pWrite = mz_zip_file_write_func; - pZip->m_pNeeds_keepalive = NULL; -#endif /* #ifdef MINIZ_NO_STDIO */ - } else if (pState->m_pMem) { - /* Archive lives in a memory block. Assume it's from the heap that we can - * resize using the realloc callback. */ - if (pZip->m_pIO_opaque != pZip) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - pState->m_mem_capacity = pState->m_mem_size; - pZip->m_pWrite = mz_zip_heap_write_func; - pZip->m_pNeeds_keepalive = NULL; - } - /* Archive is being read via a user provided read function - make sure the - user has specified a write function too. */ - else if (!pZip->m_pWrite) - return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); - - /* Start writing new files at the archive's current central directory - * location. */ - /* TODO: We could add a flag that lets the user start writing immediately - * AFTER the existing central dir - this would be safer. */ - pZip->m_archive_size = pZip->m_central_directory_file_ofs; - pZip->m_central_directory_file_ofs = 0; - - /* Clear the sorted central dir offsets, they aren't useful or maintained now. - */ - /* Even though we're now in write mode, files can still be extracted and - * verified, but file locates will be slow. */ - /* TODO: We could easily maintain the sorted central directory offsets. */ - mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); - - pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; - - return MZ_TRUE; -} - mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) { return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); @@ -8560,6 +8492,8 @@ mz_bool mz_zip_writer_add_mem_ex_v2( time(&cur_time); mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); } +#else + (void)last_modified; #endif /* #ifndef MINIZ_NO_TIME */ if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { @@ -8888,6 +8822,8 @@ mz_bool mz_zip_writer_add_read_buf_callback( if (pFile_time) { mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); } +#else + (void)pFile_time; #endif if (max_size <= 3) @@ -9414,9 +9350,9 @@ mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); - src_archive_bytes_remaining = local_header_filename_size + - local_header_extra_len + - src_file_stat.m_comp_size; + src_archive_bytes_remaining = src_file_stat.m_comp_size + + local_header_filename_size + + local_header_extra_len; /* Try to find a zip64 extended information field */ if ((local_header_extra_len) && @@ -10240,3 +10176,5 @@ mz_bool mz_zip_end(mz_zip_archive *pZip) { #endif #endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ + +#endif // MINIZ_HEADER_FILE_ONLY diff --git a/src/zip.c b/src/zip.c index a7e5ef81..2cb8467f 100644 --- a/src/zip.c +++ b/src/zip.c @@ -304,7 +304,7 @@ static int zip_archive_truncate(mz_zip_archive *pzip) { if (pzip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { if (pState->m_pFile) { int fd = fileno(pState->m_pFile); - return ftruncate(fd, file_size); + return ftruncate(fd, pState->m_file_archive_start_ofs + file_size); } } return 0; @@ -382,8 +382,8 @@ static int zip_archive_extract(mz_zip_archive *zip_archive, const char *dir, 19)) // if zip is produced on Unix or macOS (3 and 19 from // section 4.4.2.2 of zip standard) && info.m_external_attr & - (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 - // is directory) + (0x20 << 24)) { // and has sym link attribute (0x80 is file, + // 0x40 is directory) #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ defined(__MINGW32__) #else @@ -597,7 +597,6 @@ static int zip_index_update(struct zip_entry_mark_t *entry_mark, static int zip_entry_finalize(struct zip_t *zip, struct zip_entry_mark_t *entry_mark, const ssize_t n) { - ssize_t i = 0; mz_uint64 *local_header_ofs_array = (mz_uint64 *)calloc(n, sizeof(mz_uint64)); if (!local_header_ofs_array) { @@ -993,24 +992,30 @@ struct zip_t *zip_openwitherror(const char *zipname, int level, char mode, break; case 'a': - case 'd': - if (!mz_zip_reader_init_file_v2_rpb( - &(zip->archive), zipname, - zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) { - // An archive file does not exist or cannot initialize - // zip_archive reader + case 'd': { + MZ_FILE *fp = MZ_FOPEN(zipname, "r+b"); + if (!fp) { + *errnum = ZIP_EOPNFILE; + goto cleanup; + } + if (!mz_zip_reader_init_cfile( + &(zip->archive), fp, 0, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize zip_archive + // reader *errnum = ZIP_ERINIT; + fclose(fp); goto cleanup; } - if ((mode == 'a' || mode == 'd')) { - if (!mz_zip_writer_init_from_reader_v2_noreopen(&(zip->archive), zipname, - 0)) { - *errnum = ZIP_EWRINIT; - mz_zip_reader_end(&(zip->archive)); - goto cleanup; - } + if (!mz_zip_writer_init_from_reader_v2(&(zip->archive), zipname, 0)) { + *errnum = ZIP_EWRINIT; + fclose(fp); + mz_zip_reader_end(&(zip->archive)); + goto cleanup; } - break; + // The file pointer is now owned by the archive object. + zip->archive.m_zip_type = MZ_ZIP_TYPE_FILE; + } break; default: *errnum = ZIP_EINVMODE; @@ -1055,6 +1060,16 @@ int zip_is64(struct zip_t *zip) { return (int)zip->archive.m_pState->m_zip64; } +int zip_offset(struct zip_t *zip, uint64_t *offset) { + if (!zip || !zip->archive.m_pState) { + // zip_t handler or zip state is not initialized + return ZIP_ENOINIT; + } + + *offset = mz_zip_get_archive_file_start_offset(&zip->archive); + return 0; +} + static int _zip_entry_open(struct zip_t *zip, const char *entryname, int case_sensitive) { size_t entrylen = 0; @@ -1952,8 +1967,7 @@ struct zip_t *zip_cstream_openwitherror(FILE *stream, int level, char mode, goto cleanup; } if ((mode == 'a' || mode == 'd')) { - if (!mz_zip_writer_init_from_reader_v2_noreopen(&(zip->archive), NULL, - 0)) { + if (!mz_zip_writer_init_from_reader_v2(&(zip->archive), NULL, 0)) { *errnum = ZIP_EWRINIT; mz_zip_reader_end(&(zip->archive)); goto cleanup; diff --git a/src/zip.h b/src/zip.h index 92056d5c..dce99ffb 100644 --- a/src/zip.h +++ b/src/zip.h @@ -162,6 +162,16 @@ extern ZIP_EXPORT void zip_close(struct zip_t *zip); */ extern ZIP_EXPORT int zip_is64(struct zip_t *zip); +/** + * Returns the offset in the stream where the zip header is located. + * + * @param zip zip archive handler. + * @param offset zip header offset. + * + * @return the return code - 0 if successful, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_offset(struct zip_t *zip, uint64_t *offset); + /** * Opens an entry by name in the zip archive. * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ed3089c..1bdfccdb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,3 +62,9 @@ add_executable(${test_open_out} test_open.c) target_link_libraries(${test_open_out} zip) add_test(NAME ${test_open_out} COMMAND ${test_open_out}) add_sanitizers(${test_open_out}) + +set(test_offset_out test_offset.out) +add_executable(${test_offset_out} test_offset.c) +target_link_libraries(${test_offset_out} zip) +add_test(NAME ${test_offset_out} COMMAND ${test_offset_out}) +add_sanitizers(${test_offset_out}) \ No newline at end of file diff --git a/test/test_offset.c b/test/test_offset.c new file mode 100644 index 00000000..c4bf2deb --- /dev/null +++ b/test/test_offset.c @@ -0,0 +1,96 @@ +#include +#include + +#include + +#include "minunit.h" + +#if defined(_WIN32) || defined(_WIN64) +#define MKTEMP _mktemp +#define UNLINK _unlink +#else +#define MKTEMP mkstemp +#define UNLINK unlink +#endif + +static char ZIPNAME[L_tmpnam + 1] = {0}; + +#define HEADERDATA1 "this precedes the zip header" +#define TESTDATA1 "Some test data 1...\0" +#define TESTDATA2 "Some test data 2...\0" +#define CRC32DATA1 2220805626 +#define CRC32DATA2 2532008468 + +void test_setup(void) { + strncpy(ZIPNAME, "z-XXXXXX\0", L_tmpnam); + MKTEMP(ZIPNAME); + + FILE *fp = fopen(ZIPNAME, "wb"); + mu_check(fp != NULL); + fwrite(HEADERDATA1, 1, strlen(HEADERDATA1), fp); + + struct zip_t *zip = zip_cstream_open(fp, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); + mu_check(zip != NULL); + + mu_assert_int_eq(0, zip_entry_open(zip, "test/test-1.txt")); + mu_assert_int_eq(0, zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1))); + mu_assert_int_eq(0, zip_entry_close(zip)); + + zip_close(zip); + fclose(fp); + + zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'a'); + mu_check(zip != NULL); + + mu_assert_int_eq(0, zip_entry_open(zip, "test/test-2.txt")); + mu_assert_int_eq(0, zip_entry_write(zip, TESTDATA2, strlen(TESTDATA2))); + mu_assert_int_eq(0, zip_entry_close(zip)); + + zip_close(zip); +} + +void test_teardown(void) { remove(ZIPNAME); } + +MU_TEST(test_read) { + char *buf = NULL; + ssize_t bufsize; + size_t buftmp; + uint64_t archive_offset; + + struct zip_t *zip = zip_open(ZIPNAME, 0, 'r'); + mu_check(zip != NULL); + + zip_offset(zip, &archive_offset); + mu_assert_int_eq(strlen(HEADERDATA1), archive_offset); + + mu_assert_int_eq(2, zip_entries_total(zip)); + + mu_assert_int_eq(0, zip_entry_open(zip, "test/test-1.txt")); + mu_assert_int_eq(strlen(TESTDATA1), zip_entry_size(zip)); + mu_check(CRC32DATA1 == zip_entry_crc32(zip)); + bufsize = zip_entry_read(zip, (void **)&buf, &buftmp); + mu_assert_int_eq(strlen(TESTDATA1), bufsize); + mu_assert_int_eq((size_t)bufsize, buftmp); + mu_assert_int_eq(0, strncmp(buf, TESTDATA1, bufsize)); + mu_assert_int_eq(0, zip_entry_close(zip)); + zip_close(zip); + free(buf); + buf = NULL; +} + +MU_TEST_SUITE(test_offset_suite) { + MU_SUITE_CONFIGURE(&test_setup, &test_teardown); + + MU_RUN_TEST(test_read); +} + +#define UNUSED(x) (void)x + +int main(int argc, char *argv[]) { + UNUSED(argc); + UNUSED(argv); + + MU_RUN_SUITE(test_offset_suite); + MU_REPORT(); + return MU_EXIT_CODE; +} \ No newline at end of file