diff --git a/include/ziparchive/zip_archive.h b/include/ziparchive/zip_archive.h index 4f68c3b36..fc845a44e 100644 --- a/include/ziparchive/zip_archive.h +++ b/include/ziparchive/zip_archive.h @@ -130,6 +130,8 @@ int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle); int32_t OpenArchiveFd(const int fd, const char* debugFileName, ZipArchiveHandle *handle, bool assume_ownership = true); +int32_t OpenArchiveFromMemory(void* address, size_t length, const char* debugFileName, + ZipArchiveHandle *handle); /* * Close archive, releasing resources associated with it. This will * unmap the central directory of the zipfile and free all internal @@ -214,6 +216,17 @@ int GetFileDescriptor(const ZipArchiveHandle handle); const char* ErrorCodeString(int32_t error_code); +#if !defined(_WIN32) +typedef bool (*ProcessZipEntryFunction)(const uint8_t* buf, size_t buf_size, void* cookie); + +/* + * Stream the uncompressed data through the supplied function, + * passing cookie to it each time it gets called. +*/ +int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry, + ProcessZipEntryFunction func, void* cookie); +#endif + __END_DECLS #endif // LIBZIPARCHIVE_ZIPARCHIVE_H_ diff --git a/libziparchive/testdata/dummy-update.zip b/libziparchive/testdata/dummy-update.zip new file mode 100644 index 000000000..6976bf155 Binary files /dev/null and b/libziparchive/testdata/dummy-update.zip differ diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc index cc3f0e4d7..b00557cfc 100644 --- a/libziparchive/zip_archive.cc +++ b/libziparchive/zip_archive.cc @@ -215,19 +215,14 @@ static int32_t AddToHash(ZipString *hash_table, const uint64_t hash_table_size, return 0; } -static int32_t MapCentralDirectory0(int fd, const char* debug_file_name, - ZipArchive* archive, off64_t file_length, - off64_t read_amount, uint8_t* scan_buffer) { +static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* archive, + off64_t file_length, off64_t read_amount, + uint8_t* scan_buffer) { const off64_t search_start = file_length - read_amount; - if (lseek64(fd, search_start, SEEK_SET) != search_start) { - ALOGW("Zip: seek %" PRId64 " failed: %s", static_cast(search_start), - strerror(errno)); - return kIoError; - } - if (!android::base::ReadFully(fd, scan_buffer, static_cast(read_amount))) { - ALOGW("Zip: read %" PRId64 " failed: %s", static_cast(read_amount), - strerror(errno)); + if(!archive->mapped_zip.ReadAtOffset(scan_buffer, read_amount, search_start)) { + ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed", + static_cast(read_amount), static_cast(search_start)); return kIoError; } @@ -287,9 +282,11 @@ static int32_t MapCentralDirectory0(int fd, const char* debug_file_name, * It all looks good. Create a mapping for the CD, and set the fields * in archive. */ - if (!archive->directory_map.create(debug_file_name, fd, - static_cast(eocd->cd_start_offset), - static_cast(eocd->cd_size), true /* read only */) ) { + + if (!archive->InitializeCentralDirectory(debug_file_name, + static_cast(eocd->cd_start_offset), + static_cast(eocd->cd_size))) { + ALOGE("Zip: failed to intialize central directory.\n"); return kMmapFailed; } @@ -304,18 +301,16 @@ static int32_t MapCentralDirectory0(int fd, const char* debug_file_name, * * On success, returns 0 after populating fields from the EOCD area: * directory_offset - * directory_map + * directory_ptr * num_entries */ -static int32_t MapCentralDirectory(int fd, const char* debug_file_name, - ZipArchive* archive) { +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). - off64_t file_length = lseek64(fd, 0, SEEK_END); + off64_t file_length = archive->mapped_zip.GetFileLength(); if (file_length == -1) { - ALOGV("Zip: lseek on fd %d failed", fd); return kInvalidFile; } @@ -346,11 +341,9 @@ static int32_t MapCentralDirectory(int fd, const char* debug_file_name, read_amount = file_length; } - uint8_t* scan_buffer = reinterpret_cast(malloc(read_amount)); - int32_t result = MapCentralDirectory0(fd, debug_file_name, archive, - file_length, read_amount, scan_buffer); - - free(scan_buffer); + std::vector scan_buffer(read_amount); + int32_t result = MapCentralDirectory0(debug_file_name, archive, file_length, read_amount, + scan_buffer.data()); return result; } @@ -361,9 +354,8 @@ static int32_t MapCentralDirectory(int fd, const char* debug_file_name, * Returns 0 on success. */ static int32_t ParseZipArchive(ZipArchive* archive) { - const uint8_t* const cd_ptr = - reinterpret_cast(archive->directory_map.getDataPtr()); - const size_t cd_length = archive->directory_map.getDataLength(); + 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; /* @@ -437,7 +429,7 @@ static int32_t ParseZipArchive(ZipArchive* archive) { static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) { int32_t result = -1; - if ((result = MapCentralDirectory(archive->fd, debug_file_name, archive))) { + if ((result = MapCentralDirectory(debug_file_name, archive)) != 0) { return result; } @@ -468,6 +460,13 @@ int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle) { return OpenArchiveInternal(archive, fileName); } +int32_t OpenArchiveFromMemory(void* address, size_t length, const char* debug_file_name, + ZipArchiveHandle *handle) { + ZipArchive* archive = new ZipArchive(address, length); + *handle = archive; + return OpenArchiveInternal(archive, debug_file_name); +} + /* * Close a ZipArchive, closing the file and freeing the contents. */ @@ -477,10 +476,10 @@ void CloseArchive(ZipArchiveHandle handle) { delete archive; } -static int32_t UpdateEntryFromDataDescriptor(int fd, +static int32_t UpdateEntryFromDataDescriptor(MappedZipFile& mapped_zip, ZipEntry *entry) { uint8_t ddBuf[sizeof(DataDescriptor) + sizeof(DataDescriptor::kOptSignature)]; - if (!android::base::ReadFully(fd, ddBuf, sizeof(ddBuf))) { + if (!mapped_zip.ReadData(ddBuf, sizeof(ddBuf))) { return kIoError; } @@ -495,23 +494,6 @@ static int32_t UpdateEntryFromDataDescriptor(int fd, return 0; } -// Attempts to read |len| bytes into |buf| at offset |off|. -// On non-Windows platforms, callers are guaranteed that the |fd| -// offset is unchanged and there is no side effect to this call. -// -// On Windows platforms this is not thread-safe. -static inline bool ReadAtOffset(int fd, uint8_t* buf, size_t len, off64_t off) { -#if !defined(_WIN32) - return TEMP_FAILURE_RETRY(pread64(fd, buf, len, off)); -#else - if (lseek64(fd, off, SEEK_SET) != off) { - ALOGW("Zip: failed seek to offset %" PRId64, off); - return false; - } - return android::base::ReadFully(fd, buf, len); -#endif -} - static int32_t FindEntry(const ZipArchive* archive, const int ent, ZipEntry* data) { const uint16_t nameLen = archive->hash_table[ent].name_length; @@ -525,9 +507,8 @@ static int32_t FindEntry(const ZipArchive* archive, const int ent, // This is the base of our mmapped region, we have to sanity check that // the name that's in the hash table is a pointer to a location within // this mapped region. - const uint8_t* base_ptr = reinterpret_cast( - archive->directory_map.getDataPtr()); - if (ptr < base_ptr || ptr > base_ptr + archive->directory_map.getDataLength()) { + const uint8_t* base_ptr = archive->central_directory.GetBasePtr(); + if (ptr < base_ptr || ptr > base_ptr + archive->central_directory.GetMapLength()) { ALOGW("Zip: Invalid entry pointer"); return kInvalidOffset; } @@ -559,7 +540,7 @@ static int32_t FindEntry(const ZipArchive* archive, const int ent, } uint8_t lfh_buf[sizeof(LocalFileHeader)]; - if (!ReadAtOffset(archive->fd, lfh_buf, sizeof(lfh_buf), local_header_offset)) { + if (!archive->mapped_zip.ReadAtOffset(lfh_buf, sizeof(lfh_buf), local_header_offset)) { ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast(local_header_offset)); return kIoError; @@ -599,19 +580,16 @@ static int32_t FindEntry(const ZipArchive* archive, const int ent, return kInvalidOffset; } - uint8_t* name_buf = reinterpret_cast(malloc(nameLen)); - if (!ReadAtOffset(archive->fd, name_buf, nameLen, name_offset)) { + std::vector name_buf(nameLen); + if (!archive->mapped_zip.ReadAtOffset(name_buf.data(), nameLen, name_offset)) { ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast(name_offset)); - free(name_buf); return kIoError; } - if (memcmp(archive->hash_table[ent].name, name_buf, nameLen)) { - free(name_buf); + if (memcmp(archive->hash_table[ent].name, name_buf.data(), nameLen)) { return kInconsistentInformation; } - free(name_buf); } else { ALOGW("Zip: lfh name did not match central directory."); return kInconsistentInformation; @@ -881,7 +859,7 @@ static inline int zlib_inflateInit2(z_stream* stream, int window_bits) { } #pragma GCC diagnostic pop -static int32_t InflateEntryToWriter(int fd, const ZipEntry* entry, +static int32_t InflateEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry, Writer* writer, uint64_t* crc_out) { const size_t kBufSize = 32768; std::vector read_buf(kBufSize); @@ -931,7 +909,7 @@ static int32_t InflateEntryToWriter(int fd, const ZipEntry* entry, /* read as much as we can */ if (zstream.avail_in == 0) { const size_t getSize = (compressed_length > kBufSize) ? kBufSize : compressed_length; - if (!android::base::ReadFully(fd, read_buf.data(), getSize)) { + if (!mapped_zip.ReadData(read_buf.data(), getSize)) { ALOGW("Zip: inflate read failed, getSize = %zu: %s", getSize, strerror(errno)); return kIoError; } @@ -979,7 +957,7 @@ static int32_t InflateEntryToWriter(int fd, const ZipEntry* entry, return 0; } -static int32_t CopyEntryToWriter(int fd, const ZipEntry* entry, Writer* writer, +static int32_t CopyEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry, Writer* writer, uint64_t *crc_out) { static const uint32_t kBufSize = 32768; std::vector buf(kBufSize); @@ -993,7 +971,7 @@ static int32_t CopyEntryToWriter(int fd, const ZipEntry* entry, Writer* writer, // Safe conversion because kBufSize is narrow enough for a 32 bit signed // value. const size_t block_size = (remaining > kBufSize) ? kBufSize : remaining; - if (!android::base::ReadFully(fd, buf.data(), block_size)) { + if (!mapped_zip.ReadData(buf.data(), block_size)) { ALOGW("CopyFileToFile: copy read failed, block_size = %zu: %s", block_size, strerror(errno)); return kIoError; } @@ -1016,7 +994,7 @@ int32_t ExtractToWriter(ZipArchiveHandle handle, const uint16_t method = entry->method; off64_t data_offset = entry->offset; - if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) { + if (!archive->mapped_zip.SeekToOffset(data_offset)) { ALOGW("Zip: lseek to data at %" PRId64 " failed", static_cast(data_offset)); return kIoError; } @@ -1025,13 +1003,13 @@ int32_t ExtractToWriter(ZipArchiveHandle handle, int32_t return_value = -1; uint64_t crc = 0; if (method == kCompressStored) { - return_value = CopyEntryToWriter(archive->fd, entry, writer, &crc); + return_value = CopyEntryToWriter(archive->mapped_zip, entry, writer, &crc); } else if (method == kCompressDeflated) { - return_value = InflateEntryToWriter(archive->fd, entry, writer, &crc); + return_value = InflateEntryToWriter(archive->mapped_zip, entry, writer, &crc); } if (!return_value && entry->has_data_descriptor) { - return_value = UpdateEntryFromDataDescriptor(archive->fd, entry); + return_value = UpdateEntryFromDataDescriptor(archive->mapped_zip, entry); if (return_value) { return return_value; } @@ -1072,7 +1050,7 @@ const char* ErrorCodeString(int32_t error_code) { } int GetFileDescriptor(const ZipArchiveHandle handle) { - return reinterpret_cast(handle)->fd; + return reinterpret_cast(handle)->mapped_zip.GetFileDescriptor(); } ZipString::ZipString(const char* entry_name) @@ -1081,3 +1059,143 @@ ZipString::ZipString(const char* entry_name) CHECK_LE(len, static_cast(UINT16_MAX)); name_length = static_cast(len); } + +#if !defined(_WIN32) +class ProcessWriter : public Writer { + public: + ProcessWriter(ProcessZipEntryFunction func, void* cookie) : Writer(), + proc_function_(func), + cookie_(cookie) { + } + + virtual bool Append(uint8_t* buf, size_t buf_size) override { + return proc_function_(buf, buf_size, cookie_); + } + + private: + ProcessZipEntryFunction proc_function_; + void* cookie_; +}; + +int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry, + ProcessZipEntryFunction func, void* cookie) { + ProcessWriter writer(func, cookie); + return ExtractToWriter(handle, entry, &writer); +} + +#endif //!defined(_WIN32) + +int MappedZipFile::GetFileDescriptor() const { + if (!has_fd_) { + ALOGW("Zip: MappedZipFile doesn't have a file descriptor."); + return -1; + } + return fd_; +} + +void* MappedZipFile::GetBasePtr() const { + if (has_fd_) { + ALOGW("Zip: MappedZipFile doesn't have a base pointer."); + return nullptr; + } + return base_ptr_; +} + +off64_t MappedZipFile::GetFileLength() const { + if (has_fd_) { + off64_t result = lseek64(fd_, 0, SEEK_END); + if (result == -1) { + ALOGE("Zip: lseek on fd %d failed: %s", fd_, strerror(errno)); + } + return result; + } else { + if (base_ptr_ == nullptr) { + ALOGE("Zip: invalid file map\n"); + return -1; + } + return static_cast(data_length_); + } +} + +bool MappedZipFile::SeekToOffset(off64_t offset) { + if (has_fd_) { + if (lseek64(fd_, offset, SEEK_SET) != offset) { + ALOGE("Zip: lseek to %" PRId64 " failed: %s\n", offset, strerror(errno)); + return false; + } + return true; + } else { + if (offset < 0 || offset > static_cast(data_length_)) { + ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64 "\n" , offset, + data_length_); + return false; + } + + read_pos_ = offset; + return true; + } +} + +bool MappedZipFile::ReadData(uint8_t* buffer, size_t read_amount) { + if (has_fd_) { + if(!android::base::ReadFully(fd_, buffer, read_amount)) { + ALOGE("Zip: read from %d failed\n", fd_); + return false; + } + } else { + memcpy(buffer, static_cast(base_ptr_) + read_pos_, read_amount); + read_pos_ += read_amount; + } + return true; +} + +// Attempts to read |len| bytes into |buf| at offset |off|. +bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) { +#if !defined(_WIN32) + if (has_fd_) { + if (static_cast(TEMP_FAILURE_RETRY(pread64(fd_, buf, len, off))) != len) { + ALOGE("Zip: failed to read at offset %" PRId64 "\n", off); + return false; + } + return true; + } +#endif + if (!SeekToOffset(off)) { + return false; + } + return ReadData(buf, len); + +} + +void CentralDirectory::Initialize(void* map_base_ptr, off64_t cd_start_offset, size_t cd_size) { + base_ptr_ = static_cast(map_base_ptr) + cd_start_offset; + length_ = cd_size; +} + +bool ZipArchive::InitializeCentralDirectory(const char* debug_file_name, off64_t cd_start_offset, + size_t cd_size) { + if (mapped_zip.HasFd()) { + if (!directory_map->create(debug_file_name, mapped_zip.GetFileDescriptor(), + cd_start_offset, cd_size, true /* read only */)) { + return false; + } + + CHECK_EQ(directory_map->getDataLength(), cd_size); + central_directory.Initialize(directory_map->getDataPtr(), 0/*offset*/, cd_size); + } else { + if (mapped_zip.GetBasePtr() == nullptr) { + ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer\n"); + return false; + } + if (static_cast(cd_start_offset) + static_cast(cd_size) > + mapped_zip.GetFileLength()) { + ALOGE("Zip: Failed to map central directory, offset exceeds mapped memory region (" + "start_offset %" PRId64 ", cd_size %zu, mapped_region_size %" PRId64 ")", + static_cast(cd_start_offset), cd_size, mapped_zip.GetFileLength()); + return false; + } + + central_directory.Initialize(mapped_zip.GetBasePtr(), cd_start_offset, cd_size); + } + return true; +} diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h index ab5236889..971db4f12 100644 --- a/libziparchive/zip_archive_private.h +++ b/libziparchive/zip_archive_private.h @@ -21,17 +21,83 @@ #include #include +#include +#include + #include #include +class MappedZipFile { + public: + explicit MappedZipFile(const int fd) : + has_fd_(true), + fd_(fd), + base_ptr_(nullptr), + data_length_(0), + read_pos_(0) {} + + explicit MappedZipFile(void* address, size_t length) : + has_fd_(false), + fd_(-1), + base_ptr_(address), + data_length_(static_cast(length)), + read_pos_(0) {} + + bool HasFd() const {return has_fd_;} + + int GetFileDescriptor() const; + + void* GetBasePtr() const; + + off64_t GetFileLength() const; + + bool SeekToOffset(off64_t offset); + + bool ReadData(uint8_t* buffer, size_t read_amount); + + bool ReadAtOffset(uint8_t* buf, size_t len, off64_t off); + + private: + // If has_fd_ is true, fd is valid and we'll read contents of a zip archive + // from the file. Otherwise, we're opening the archive from a memory mapped + // file. In that case, base_ptr_ points to the start of the memory region and + // data_length_ defines the file length. + const bool has_fd_; + + const int fd_; + + void* const base_ptr_; + const off64_t data_length_; + // read_pos_ is the offset to the base_ptr_ where we read data from. + size_t read_pos_; +}; + +class CentralDirectory { + public: + CentralDirectory(void) : + base_ptr_(nullptr), + length_(0) {} + + const uint8_t* GetBasePtr() const {return base_ptr_;} + + size_t GetMapLength() const {return length_;} + + void Initialize(void* map_base_ptr, off64_t cd_start_offset, size_t cd_size); + + private: + const uint8_t* base_ptr_; + size_t length_; +}; + struct ZipArchive { // open Zip archive - const int fd; + mutable MappedZipFile mapped_zip; const bool close_file; // mapped central directory area off64_t directory_offset; - android::FileMap directory_map; + CentralDirectory central_directory; + std::unique_ptr directory_map; // number of entries in the Zip archive uint16_t num_entries; @@ -44,20 +110,36 @@ struct ZipArchive { ZipString* hash_table; ZipArchive(const int fd, bool assume_ownership) : - fd(fd), - close_file(assume_ownership), - directory_offset(0), - num_entries(0), - hash_table_size(0), - hash_table(NULL) {} + mapped_zip(fd), + close_file(assume_ownership), + directory_offset(0), + central_directory(), + directory_map(new android::FileMap()), + num_entries(0), + hash_table_size(0), + hash_table(nullptr) {} + + ZipArchive(void* address, size_t length) : + mapped_zip(address, length), + close_file(false), + directory_offset(0), + central_directory(), + directory_map(new android::FileMap()), + num_entries(0), + hash_table_size(0), + hash_table(nullptr) {} ~ZipArchive() { - if (close_file && fd >= 0) { - close(fd); + if (close_file && mapped_zip.GetFileDescriptor() >= 0) { + close(mapped_zip.GetFileDescriptor()); } free(hash_table); } + + bool InitializeCentralDirectory(const char* debug_file_name, off64_t cd_start_offset, + size_t cd_size); + }; #endif // LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_ diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc index 41988bcc9..64b24c36e 100644 --- a/libziparchive/zip_archive_stream_entry.cc +++ b/libziparchive/zip_archive_stream_entry.cc @@ -39,7 +39,7 @@ static constexpr size_t kBufSize = 65535; bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) { ZipArchive* archive = reinterpret_cast(handle_); off64_t data_offset = entry.offset; - if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) { + if (!archive->mapped_zip.SeekToOffset(data_offset)) { ALOGW("lseek to data at %" PRId64 " failed: %s", data_offset, strerror(errno)); return false; } @@ -88,7 +88,7 @@ const std::vector* ZipArchiveStreamEntryUncompressed::Read() { size_t bytes = (length_ > data_.size()) ? data_.size() : length_; ZipArchive* archive = reinterpret_cast(handle_); errno = 0; - if (!android::base::ReadFully(archive->fd, data_.data(), bytes)) { + if (!archive->mapped_zip.ReadData(data_.data(), bytes)) { if (errno != 0) { ALOGE("Error reading from archive fd: %s", strerror(errno)); } else { @@ -209,7 +209,7 @@ const std::vector* ZipArchiveStreamEntryCompressed::Read() { size_t bytes = (compressed_length_ > in_.size()) ? in_.size() : compressed_length_; ZipArchive* archive = reinterpret_cast(handle_); errno = 0; - if (!android::base::ReadFully(archive->fd, in_.data(), bytes)) { + if (!archive->mapped_zip.ReadData(in_.data(), bytes)) { if (errno != 0) { ALOGE("Error reading from archive fd: %s", strerror(errno)); } else { diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc index 6aee1bbdf..9dd6cc0df 100644 --- a/libziparchive/zip_archive_test.cc +++ b/libziparchive/zip_archive_test.cc @@ -26,7 +26,9 @@ #include #include +#include #include +#include #include #include @@ -36,6 +38,7 @@ static const std::string kMissingZip = "missing.zip"; static const std::string kValidZip = "valid.zip"; static const std::string kLargeZip = "large.zip"; static const std::string kBadCrcZip = "bad_crc.zip"; +static const std::string kUpdateZip = "dummy-update.zip"; static const std::vector kATxtContents { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', @@ -494,6 +497,32 @@ TEST(ziparchive, ExtractToFile) { lseek64(tmp_file.fd, 0, SEEK_END)); } +#if !defined(_WIN32) +TEST(ziparchive, OpenFromMemory) { + const std::string zip_path = test_data_dir + "/" + kUpdateZip; + android::base::unique_fd fd(open(zip_path.c_str(), O_RDONLY | O_BINARY)); + ASSERT_NE(-1, fd); + struct stat sb; + ASSERT_EQ(0, fstat(fd, &sb)); + + // Memory map the file first and open the archive from the memory region. + android::FileMap file_map; + file_map.create(zip_path.c_str(), fd, 0/*offset*/, sb.st_size, true); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFromMemory(file_map.getDataPtr(), file_map.getDataLength(), + zip_path.c_str(), &handle)); + + // Assert one entry can be found and extracted correctly. + std::string BINARY_PATH("META-INF/com/google/android/update-binary"); + ZipString binary_path(BINARY_PATH.c_str()); + ZipEntry binary_entry; + ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry)); + TemporaryFile tmp_binary; + ASSERT_NE(-1, tmp_binary.fd); + ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd)); +} +#endif + static void ZipArchiveStreamTest( ZipArchiveHandle& handle, const std::string& entry_name, bool raw, bool verified, ZipEntry* entry, std::vector* read_data) {