Merge "Add definition for zip64 struct"
This commit is contained in:
commit
18c00858d2
5 changed files with 205 additions and 61 deletions
|
@ -145,7 +145,7 @@ struct ZipArchiveInfo {
|
|||
/** The size in bytes of the archive itself. Used by zipinfo. */
|
||||
off64_t archive_size;
|
||||
/** The number of entries in the archive. */
|
||||
size_t entry_count;
|
||||
uint64_t entry_count;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,6 +65,10 @@ static const bool kCrcChecksEnabled = false;
|
|||
// The maximum number of bytes to scan backwards for the EOCD start.
|
||||
static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);
|
||||
|
||||
// Set a reasonable cap (256 GiB) for the zip file size. So the data is always valid when
|
||||
// we parse the fields in cd or local headers as 64 bits signed integers.
|
||||
static constexpr uint64_t kMaxFileLength = 256 * static_cast<uint64_t>(1u << 30u);
|
||||
|
||||
/*
|
||||
* A Read-only Zip archive.
|
||||
*
|
||||
|
@ -125,12 +129,27 @@ ZipArchive::~ZipArchive() {
|
|||
}
|
||||
}
|
||||
|
||||
static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* archive,
|
||||
off64_t file_length, uint32_t read_amount,
|
||||
uint8_t* scan_buffer) {
|
||||
struct CentralDirectoryInfo {
|
||||
uint64_t num_records;
|
||||
// The size of the central directory (in bytes).
|
||||
uint64_t cd_size;
|
||||
// The offset of the start of the central directory, relative
|
||||
// to the start of the file.
|
||||
uint64_t cd_start_offset;
|
||||
};
|
||||
|
||||
static ZipError FindCentralDirectoryInfoForZip64(CentralDirectoryInfo* /* cdInfo */) {
|
||||
ALOGW("Zip: Parsing zip64 EOCD isn't supported yet.");
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
static ZipError FindCentralDirectoryInfo(const char* debug_file_name, ZipArchive* archive,
|
||||
off64_t file_length, uint32_t read_amount,
|
||||
CentralDirectoryInfo* cdInfo) {
|
||||
std::vector<uint8_t> scan_buffer(read_amount);
|
||||
const off64_t search_start = file_length - read_amount;
|
||||
|
||||
if (!archive->mapped_zip.ReadAtOffset(scan_buffer, read_amount, search_start)) {
|
||||
if (!archive->mapped_zip.ReadAtOffset(scan_buffer.data(), read_amount, search_start)) {
|
||||
ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed", static_cast<int64_t>(read_amount),
|
||||
static_cast<int64_t>(search_start));
|
||||
return kIoError;
|
||||
|
@ -159,7 +178,7 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
|
|||
}
|
||||
|
||||
const off64_t eocd_offset = search_start + i;
|
||||
const EocdRecord* eocd = reinterpret_cast<const EocdRecord*>(scan_buffer + i);
|
||||
auto eocd = reinterpret_cast<const EocdRecord*>(scan_buffer.data() + i);
|
||||
/*
|
||||
* Verify that there's no trailing space at the end of the central directory
|
||||
* and its comment.
|
||||
|
@ -171,6 +190,13 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
|
|||
return kInvalidFile;
|
||||
}
|
||||
|
||||
// One of the field is 0xFFFFFFFF, look for the zip64 EOCD instead.
|
||||
if (eocd->cd_size == UINT32_MAX || eocd->cd_start_offset == UINT32_MAX) {
|
||||
ALOGV("Looking for the zip64 EOCD, cd_size: %" PRIu32 "cd_start_offset: %" PRId32,
|
||||
eocd->cd_size, eocd->cd_start_offset);
|
||||
return FindCentralDirectoryInfoForZip64(cdInfo);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab the CD offset and size, and the number of entries in the
|
||||
* archive and verify that they look reasonable.
|
||||
|
@ -180,47 +206,29 @@ static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* arc
|
|||
eocd->cd_start_offset, eocd->cd_size, static_cast<int64_t>(eocd_offset));
|
||||
return kInvalidOffset;
|
||||
}
|
||||
if (eocd->num_records == 0) {
|
||||
#if defined(__ANDROID__)
|
||||
ALOGW("Zip: empty archive?");
|
||||
#endif
|
||||
return kEmptyArchive;
|
||||
}
|
||||
|
||||
ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32, eocd->num_records,
|
||||
eocd->cd_size, eocd->cd_start_offset);
|
||||
|
||||
// It all looks good. Create a mapping for the CD, and set the fields
|
||||
// in archive.
|
||||
if (!archive->InitializeCentralDirectory(static_cast<off64_t>(eocd->cd_start_offset),
|
||||
static_cast<size_t>(eocd->cd_size))) {
|
||||
return kMmapFailed;
|
||||
}
|
||||
|
||||
archive->num_entries = eocd->num_records;
|
||||
archive->directory_offset = eocd->cd_start_offset;
|
||||
|
||||
return 0;
|
||||
*cdInfo = {.num_records = eocd->num_records,
|
||||
.cd_size = eocd->cd_size,
|
||||
.cd_start_offset = eocd->cd_start_offset};
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the zip Central Directory and memory-map it.
|
||||
*
|
||||
* On success, returns 0 after populating fields from the EOCD area:
|
||||
* On success, returns kSuccess after populating fields from the EOCD area:
|
||||
* directory_offset
|
||||
* directory_ptr
|
||||
* num_entries
|
||||
*/
|
||||
static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
|
||||
// Test file length. We use lseek64 to make sure the file
|
||||
// is small enough to be a zip file (Its size must be less than
|
||||
// 0xffffffff bytes).
|
||||
static ZipError MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
|
||||
// Test file length. We use lseek64 to make sure the file is small enough to be a zip file.
|
||||
off64_t file_length = archive->mapped_zip.GetFileLength();
|
||||
if (file_length == -1) {
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
if (file_length > static_cast<off64_t>(0xffffffff)) {
|
||||
if (file_length > kMaxFileLength) {
|
||||
ALOGV("Zip: zip file too long %" PRId64, static_cast<int64_t>(file_length));
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
@ -247,10 +255,39 @@ static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* arch
|
|||
read_amount = static_cast<uint32_t>(file_length);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> scan_buffer(read_amount);
|
||||
int32_t result =
|
||||
MapCentralDirectory0(debug_file_name, archive, file_length, read_amount, scan_buffer.data());
|
||||
return result;
|
||||
CentralDirectoryInfo cdInfo = {};
|
||||
if (auto result =
|
||||
FindCentralDirectoryInfo(debug_file_name, archive, file_length, read_amount, &cdInfo);
|
||||
result != kSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cdInfo.num_records == 0) {
|
||||
#if defined(__ANDROID__)
|
||||
ALOGW("Zip: empty archive?");
|
||||
#endif
|
||||
return kEmptyArchive;
|
||||
}
|
||||
|
||||
if (cdInfo.cd_size >= SIZE_MAX) {
|
||||
ALOGW("Zip: The size of central directory doesn't fit in range of size_t: %" PRIu64,
|
||||
cdInfo.cd_size);
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
ALOGV("+++ num_entries=%" PRIu64 " dir_size=%" PRIu64 " dir_offset=%" PRIu64, cdInfo.num_records,
|
||||
cdInfo.cd_size, cdInfo.cd_start_offset);
|
||||
|
||||
// It all looks good. Create a mapping for the CD, and set the fields in archive.
|
||||
if (!archive->InitializeCentralDirectory(static_cast<off64_t>(cdInfo.cd_start_offset),
|
||||
static_cast<size_t>(cdInfo.cd_size))) {
|
||||
return kMmapFailed;
|
||||
}
|
||||
|
||||
archive->num_entries = cdInfo.num_records;
|
||||
archive->directory_offset = cdInfo.cd_start_offset;
|
||||
|
||||
return kSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -262,13 +299,12 @@ static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* arch
|
|||
static int32_t ParseZipArchive(ZipArchive* archive) {
|
||||
const uint8_t* const cd_ptr = archive->central_directory.GetBasePtr();
|
||||
const size_t cd_length = archive->central_directory.GetMapLength();
|
||||
const uint16_t num_entries = archive->num_entries;
|
||||
const uint64_t num_entries = archive->num_entries;
|
||||
|
||||
// TODO(xunchang) parse the zip64 Eocd
|
||||
if (num_entries > UINT16_MAX) {
|
||||
archive->cd_entry_map = CdEntryMapZip64::Create();
|
||||
if (num_entries <= UINT16_MAX) {
|
||||
archive->cd_entry_map = CdEntryMapZip32::Create(static_cast<uint16_t>(num_entries));
|
||||
} else {
|
||||
archive->cd_entry_map = CdEntryMapZip32::Create(num_entries);
|
||||
archive->cd_entry_map = CdEntryMapZip64::Create();
|
||||
}
|
||||
if (archive->cd_entry_map == nullptr) {
|
||||
return kAllocationFailed;
|
||||
|
@ -280,9 +316,9 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
|
|||
*/
|
||||
const uint8_t* const cd_end = cd_ptr + cd_length;
|
||||
const uint8_t* ptr = cd_ptr;
|
||||
for (uint16_t i = 0; i < num_entries; i++) {
|
||||
for (uint64_t i = 0; i < num_entries; i++) {
|
||||
if (ptr > cd_end - sizeof(CentralDirectoryRecord)) {
|
||||
ALOGW("Zip: ran off the end (item #%" PRIu16 ", %zu bytes of central directory)", i,
|
||||
ALOGW("Zip: ran off the end (item #%" PRIu64 ", %zu bytes of central directory)", i,
|
||||
cd_length);
|
||||
#if defined(__ANDROID__)
|
||||
android_errorWriteLog(0x534e4554, "36392138");
|
||||
|
@ -292,14 +328,7 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
|
|||
|
||||
const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
|
||||
if (cdr->record_signature != CentralDirectoryRecord::kSignature) {
|
||||
ALOGW("Zip: missed a central dir sig (at %" PRIu16 ")", i);
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
const off64_t local_header_offset = cdr->local_file_header_offset;
|
||||
if (local_header_offset >= archive->directory_offset) {
|
||||
ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu16,
|
||||
static_cast<int64_t>(local_header_offset), i);
|
||||
ALOGW("Zip: missed a central dir sig (at %" PRIu64 ")", i);
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
|
@ -308,15 +337,37 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
|
|||
const uint16_t comment_length = cdr->comment_length;
|
||||
const uint8_t* file_name = ptr + sizeof(CentralDirectoryRecord);
|
||||
|
||||
if (file_name + file_name_length > cd_end) {
|
||||
ALOGW("Zip: file name for entry %" PRIu16
|
||||
if (file_name_length >= cd_length || file_name > cd_end - file_name_length) {
|
||||
ALOGW("Zip: file name for entry %" PRIu64
|
||||
" exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
|
||||
i, file_name_length, cd_length);
|
||||
return kInvalidEntryName;
|
||||
}
|
||||
|
||||
const uint8_t* extra_field = file_name + file_name_length;
|
||||
if (extra_length >= cd_length || extra_field > cd_end - extra_length) {
|
||||
ALOGW("Zip: extra field for entry %" PRIu64
|
||||
" exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
|
||||
i, extra_length, cd_length);
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
off64_t local_header_offset = cdr->local_file_header_offset;
|
||||
if (local_header_offset == UINT32_MAX) {
|
||||
// TODO(xunchang) parse the zip64 eocd
|
||||
ALOGW("Zip: Parsing zip64 cd entry isn't supported yet");
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
if (local_header_offset >= archive->directory_offset) {
|
||||
ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu64,
|
||||
static_cast<int64_t>(local_header_offset), i);
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
// Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters.
|
||||
if (!IsValidEntryName(file_name, file_name_length)) {
|
||||
ALOGW("Zip: invalid file name at entry %" PRIu16, i);
|
||||
ALOGW("Zip: invalid file name at entry %" PRIu64, i);
|
||||
return kInvalidEntryName;
|
||||
}
|
||||
|
||||
|
@ -331,7 +382,7 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
|
|||
|
||||
ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length;
|
||||
if ((ptr - cd_ptr) > static_cast<int64_t>(cd_length)) {
|
||||
ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu16, ptr - cd_ptr, cd_length, i);
|
||||
ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu64, ptr - cd_ptr, cd_length, i);
|
||||
return kInvalidFile;
|
||||
}
|
||||
}
|
||||
|
@ -351,14 +402,14 @@ static int32_t ParseZipArchive(ZipArchive* archive) {
|
|||
return kInvalidFile;
|
||||
}
|
||||
|
||||
ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
|
||||
ALOGV("+++ zip good scan %" PRIu64 " entries", num_entries);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) {
|
||||
int32_t result = MapCentralDirectory(debug_file_name, archive);
|
||||
return result != 0 ? result : ParseZipArchive(archive);
|
||||
return result != kSuccess ? result : ParseZipArchive(archive);
|
||||
}
|
||||
|
||||
int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
|
||||
|
@ -489,7 +540,15 @@ static int32_t FindEntry(const ZipArchive* archive, std::string_view entryName,
|
|||
// Figure out the local header offset from the central directory. The
|
||||
// actual file data will begin after the local header and the name /
|
||||
// extra comments.
|
||||
const off64_t local_header_offset = cdr->local_file_header_offset;
|
||||
off64_t local_header_offset = cdr->local_file_header_offset;
|
||||
// One of the info field is UINT32_MAX, try to parse the real value in the zip64 extended info in
|
||||
// the extra field.
|
||||
if (cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX ||
|
||||
cdr->local_file_header_offset == UINT32_MAX) {
|
||||
ALOGW("Zip: Parsing zip64 local file header isn't supported yet");
|
||||
return kInvalidFile;
|
||||
}
|
||||
|
||||
if (local_header_offset + static_cast<off64_t>(sizeof(LocalFileHeader)) >= cd_offset) {
|
||||
ALOGW("Zip: bad local hdr offset in zip");
|
||||
return kInvalidOffset;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
// The "end of central directory" (EOCD) record. Each archive
|
||||
// contains exactly once such record which appears at the end of
|
||||
// the archive. It contains archive wide information like the
|
||||
|
@ -173,6 +175,89 @@ struct DataDescriptor {
|
|||
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
|
||||
} __attribute__((packed));
|
||||
|
||||
// The zip64 end of central directory locator helps to find the zip64 EOCD.
|
||||
struct Zip64EocdLocator {
|
||||
static constexpr uint32_t kSignature = 0x07064b50;
|
||||
|
||||
// The signature of zip64 eocd locator, must be |kSignature|
|
||||
uint32_t locator_signature;
|
||||
// The start disk of the zip64 eocd. This implementation assumes that each
|
||||
// archive spans a single disk only.
|
||||
uint32_t eocd_start_disk;
|
||||
// The offset offset of the zip64 end of central directory record.
|
||||
uint64_t zip64_eocd_offset;
|
||||
// The total number of disks. This implementation assumes that each archive
|
||||
// spans a single disk only.
|
||||
uint32_t num_of_disks;
|
||||
|
||||
private:
|
||||
Zip64EocdLocator() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(Zip64EocdLocator);
|
||||
} __attribute__((packed));
|
||||
|
||||
// The optional zip64 EOCD. If one of the fields in the end of central directory
|
||||
// record is too small to hold required data, the field SHOULD be set to -1
|
||||
// (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record SHOULD be created.
|
||||
struct Zip64EocdRecord {
|
||||
static constexpr uint32_t kSignature = 0x06064b50;
|
||||
|
||||
// The signature of zip64 eocd record, must be |kSignature|
|
||||
uint32_t record_signature;
|
||||
// Size of zip64 end of central directory record. It SHOULD be the size of the
|
||||
// remaining record and SHOULD NOT include the leading 12 bytes.
|
||||
uint64_t record_size;
|
||||
// The version of the tool that make this archive.
|
||||
uint16_t version_made_by;
|
||||
// Tool version needed to extract this archive.
|
||||
uint16_t version_needed;
|
||||
// Number of this disk.
|
||||
uint32_t disk_num;
|
||||
// Number of the disk with the start of the central directory.
|
||||
uint32_t cd_start_disk;
|
||||
// Total number of entries in the central directory on this disk.
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that num_records_on_disk == num_records.
|
||||
uint64_t num_records_on_disk;
|
||||
// The total number of central directory records.
|
||||
uint64_t num_records;
|
||||
// The size of the central directory in bytes.
|
||||
uint64_t cd_size;
|
||||
// The offset of the start of the central directory, relative to the start of
|
||||
// the file.
|
||||
uint64_t cd_start_offset;
|
||||
|
||||
private:
|
||||
Zip64EocdRecord() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(Zip64EocdRecord);
|
||||
} __attribute__((packed));
|
||||
|
||||
// The possible contents of the Zip64 Extended Information Extra Field. It may appear in
|
||||
// the 'extra' field of a central directory record or local file header. The order of
|
||||
// the fields in the zip64 extended information record is fixed, but the fields MUST
|
||||
// only appear if the corresponding local or central directory record field is set to
|
||||
// 0xFFFF or 0xFFFFFFFF. And this entry in the Local header MUST include BOTH original
|
||||
// and compressed file size fields.
|
||||
struct Zip64ExtendedInfo {
|
||||
static constexpr uint16_t kHeaderId = 0x0001;
|
||||
// The header tag for this 'extra' block, should be |kHeaderId|.
|
||||
uint16_t header_id;
|
||||
// The size in bytes of the remaining data (excluding the top 4 bytes).
|
||||
uint16_t data_size;
|
||||
// Size in bytes of the uncompressed file.
|
||||
std::optional<uint64_t> uncompressed_file_size;
|
||||
// Size in bytes of the compressed file.
|
||||
std::optional<uint64_t> compressed_file_size;
|
||||
// Local file header offset relative to the start of the zip file.
|
||||
std::optional<uint64_t> local_header_offset;
|
||||
|
||||
// This implementation assumes that each archive spans a single disk only. So
|
||||
// the disk_number is not used.
|
||||
// uint32_t disk_num;
|
||||
private:
|
||||
Zip64ExtendedInfo() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(Zip64ExtendedInfo);
|
||||
};
|
||||
|
||||
// mask value that signifies that the entry has a DD
|
||||
static const uint32_t kGPBDDFlagMask = 0x0008;
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ struct ZipArchive {
|
|||
std::unique_ptr<android::base::MappedFile> directory_map;
|
||||
|
||||
// number of entries in the Zip archive
|
||||
uint16_t num_entries;
|
||||
uint64_t num_entries;
|
||||
std::unique_ptr<CdEntryMapInterface> cd_entry_map;
|
||||
|
||||
ZipArchive(MappedZipFile&& map, bool assume_ownership);
|
||||
|
|
|
@ -133,8 +133,8 @@ static void MaybeShowHeader(ZipArchiveHandle zah) {
|
|||
if (!flag_1 && includes.empty() && excludes.empty()) {
|
||||
ZipArchiveInfo info{GetArchiveInfo(zah)};
|
||||
printf("Archive: %s\n", archive_name);
|
||||
printf("Zip file size: %" PRId64 " bytes, number of entries: %zu\n", info.archive_size,
|
||||
info.entry_count);
|
||||
printf("Zip file size: %" PRId64 " bytes, number of entries: %" PRIu64 "\n",
|
||||
info.archive_size, info.entry_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue