Skip to content

Commit 43bc679

Browse files
committed
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. We can actually extract the start of the archive by looking at the central directory offset, by comparing the specified and the actual file offset. The Zip64 format adds another challenge as the EOCD locator specifies the offset to the EOCD starting from the beginning of the archive. Such relative offset cannot be directly used for archives not starting at position zero, hence the addition of another step that tries to locate the 64bit EOCD right before the EOCD locator. The added overhead is pretty small (and could be made smaller by reading both the presumed EOCD and EOCD locator at once) and works pretty well for 90% of the archives I've tested it with, if that heuristic fails the old behaviour is preserved.
1 parent 8714fd3 commit 43bc679

File tree

1 file changed

+65
-12
lines changed

1 file changed

+65
-12
lines changed

Diff for: miniz_zip.c

+65-12
Original file line numberDiff line numberDiff line change
@@ -647,10 +647,23 @@ static int mz_stat64(const char *path, struct __stat64 *buffer)
647647
return MZ_TRUE;
648648
}
649649

650+
static mz_bool mz_zip_reader_eocd64_valid(mz_zip_archive *pZip, uint64_t offset, uint8_t *buf)
651+
{
652+
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)
653+
{
654+
if (MZ_READ_LE32(buf + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)
655+
{
656+
return MZ_TRUE;
657+
}
658+
}
659+
660+
return MZ_FALSE;
661+
}
662+
650663
static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags)
651664
{
652665
mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0;
653-
mz_uint64 cdir_ofs = 0;
666+
mz_uint64 cdir_ofs = 0, eocd_ofs = 0, archive_ofs = 0;
654667
mz_int64 cur_file_ofs = 0;
655668
const mz_uint8 *p;
656669

@@ -672,6 +685,7 @@ static int mz_stat64(const char *path, struct __stat64 *buffer)
672685
if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs))
673686
return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR);
674687

688+
eocd_ofs = cur_file_ofs;
675689
/* Read and verify the end of central directory record. */
676690
if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
677691
return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED);
@@ -685,21 +699,40 @@ static int mz_stat64(const char *path, struct __stat64 *buffer)
685699
{
686700
if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG)
687701
{
688-
zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
689-
if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
690-
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
691-
692-
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)
693-
{
694-
if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG)
695-
{
696-
pZip->m_pState->m_zip64 = MZ_TRUE;
697-
}
698-
}
702+
pZip->m_pState->m_zip64 = MZ_TRUE;
699703
}
700704
}
701705
}
702706

707+
if (pZip->m_pState->m_zip64)
708+
{
709+
/* Try locating the EOCD64 right before the EOCD64 locator. This works even
710+
* when the effective start of the zip header is not yet known. */
711+
if (cur_file_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE +
712+
MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)
713+
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
714+
715+
zip64_end_of_central_dir_ofs = cur_file_ofs -
716+
MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE -
717+
MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE;
718+
719+
if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs,
720+
pZip64_end_of_central_dir))
721+
{
722+
/* That failed, try reading where the locator tells us to. */
723+
zip64_end_of_central_dir_ofs = MZ_READ_LE64(
724+
pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS);
725+
726+
if (zip64_end_of_central_dir_ofs >
727+
(pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE))
728+
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
729+
730+
if (!mz_zip_reader_eocd64_valid(pZip, zip64_end_of_central_dir_ofs,
731+
pZip64_end_of_central_dir))
732+
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
733+
}
734+
}
735+
703736
pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS);
704737
cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS);
705738
num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
@@ -757,6 +790,26 @@ static int mz_stat64(const char *path, struct __stat64 *buffer)
757790
if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
758791
return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
759792

793+
/* The end of central dir follows the central dir, unless the zip file has
794+
* some trailing data (e.g. it is appended to an executable file). */
795+
archive_ofs = eocd_ofs - (cdir_ofs + cdir_size);
796+
if (pZip->m_pState->m_zip64)
797+
{
798+
if (archive_ofs < MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE +
799+
MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE)
800+
return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED);
801+
802+
archive_ofs -= MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE +
803+
MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE;
804+
}
805+
806+
/* Update the archive start position, but only if not specified. */
807+
if (pZip->m_pState->m_file_archive_start_ofs == 0)
808+
{
809+
pZip->m_pState->m_file_archive_start_ofs = archive_ofs;
810+
pZip->m_archive_size -= archive_ofs;
811+
}
812+
760813
pZip->m_central_directory_file_ofs = cdir_ofs;
761814

762815
if (pZip->m_total_files)

0 commit comments

Comments
 (0)