diff --git a/include/ziparchive/zip_archive_stream_entry.h b/include/ziparchive/zip_archive_stream_entry.h new file mode 100644 index 000000000..a40b799cf --- /dev/null +++ b/include/ziparchive/zip_archive_stream_entry.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Read-only stream access to Zip archives entries. +#ifndef LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_ +#define LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_ + +#include + +#include + +class ZipArchiveStreamEntry { + public: + virtual ~ZipArchiveStreamEntry() {} + + virtual const std::vector* Read() = 0; + + virtual bool Verify() = 0; + + static ZipArchiveStreamEntry* Create(ZipArchiveHandle handle, const ZipEntry& entry); + static ZipArchiveStreamEntry* CreateRaw(ZipArchiveHandle handle, const ZipEntry& entry); + + protected: + ZipArchiveStreamEntry(ZipArchiveHandle handle) : handle_(handle) {} + + virtual bool Init(const ZipEntry& entry); + + ZipArchiveHandle handle_; + + uint32_t crc32_; +}; + +#endif // LIBZIPARCHIVE_ZIPARCHIVESTREAMENTRY_H_ diff --git a/libziparchive/Android.mk b/libziparchive/Android.mk index 8a4921fc5..056b3e132 100644 --- a/libziparchive/Android.mk +++ b/libziparchive/Android.mk @@ -15,34 +15,46 @@ LOCAL_PATH := $(call my-dir) -source_files := zip_archive.cc zip_writer.cc -test_files := zip_archive_test.cc zip_writer_test.cc entry_name_utils_test.cc +libziparchive_source_files := \ + zip_archive.cc \ + zip_archive_stream_entry.cc \ + zip_writer.cc \ + +libziparchive_test_files := \ + entry_name_utils_test.cc \ + zip_archive_test.cc \ + zip_writer_test.cc \ # ZLIB_CONST turns on const for input buffers, which is pretty standard. -common_c_flags := -Werror -Wall -DZLIB_CONST +libziparchive_common_c_flags := \ + -DZLIB_CONST \ + -Werror \ + -Wall \ # Incorrectly warns when C++11 empty brace {} initializer is used. # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489 -common_cpp_flags := -Wold-style-cast -Wno-missing-field-initializers +libziparchive_common_cpp_flags := \ + -Wold-style-cast \ + -Wno-missing-field-initializers \ include $(CLEAR_VARS) LOCAL_CPP_EXTENSION := .cc -LOCAL_SRC_FILES := ${source_files} +LOCAL_SRC_FILES := $(libziparchive_source_files) LOCAL_STATIC_LIBRARIES := libz LOCAL_SHARED_LIBRARIES := libutils libbase LOCAL_MODULE:= libziparchive -LOCAL_CFLAGS := $(common_c_flags) -LOCAL_CPPFLAGS := $(common_cpp_flags) +LOCAL_CFLAGS := $(libziparchive_common_c_flags) +LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags) include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_CPP_EXTENSION := .cc -LOCAL_SRC_FILES := ${source_files} +LOCAL_SRC_FILES := $(libziparchive_source_files) LOCAL_STATIC_LIBRARIES := libz libutils libbase LOCAL_MODULE:= libziparchive-host -LOCAL_CFLAGS := $(common_c_flags) +LOCAL_CFLAGS := $(libziparchive_common_c_flags) LOCAL_CFLAGS_windows := -mno-ms-bitfields -LOCAL_CPPFLAGS := $(common_cpp_flags) +LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags) LOCAL_MULTILIB := both LOCAL_MODULE_HOST_OS := darwin linux windows @@ -50,12 +62,12 @@ include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_CPP_EXTENSION := .cc -LOCAL_SRC_FILES := ${source_files} +LOCAL_SRC_FILES := $(libziparchive_source_files) LOCAL_STATIC_LIBRARIES := libutils LOCAL_SHARED_LIBRARIES := libz-host liblog libbase LOCAL_MODULE:= libziparchive-host -LOCAL_CFLAGS := $(common_c_flags) -LOCAL_CPPFLAGS := $(common_cpp_flags) +LOCAL_CFLAGS := $(libziparchive_common_c_flags) +LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags) LOCAL_MULTILIB := both include $(BUILD_HOST_SHARED_LIBRARY) @@ -63,21 +75,33 @@ include $(BUILD_HOST_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := ziparchive-tests LOCAL_CPP_EXTENSION := .cc -LOCAL_CFLAGS := $(common_c_flags) -LOCAL_CPPFLAGS := $(common_cpp_flags) -LOCAL_SRC_FILES := $(test_files) -LOCAL_SHARED_LIBRARIES := liblog libbase -LOCAL_STATIC_LIBRARIES := libziparchive libz libutils +LOCAL_CFLAGS := $(libziparchive_common_c_flags) +LOCAL_CPPFLAGS := $(libziparchive_common_cpp_flags) +LOCAL_SRC_FILES := $(libziparchive_test_files) +LOCAL_SHARED_LIBRARIES := \ + libbase \ + liblog \ + +LOCAL_STATIC_LIBRARIES := \ + libziparchive \ + libz \ + libutils \ + include $(BUILD_NATIVE_TEST) include $(CLEAR_VARS) LOCAL_MODULE := ziparchive-tests-host LOCAL_CPP_EXTENSION := .cc -LOCAL_CFLAGS := $(common_c_flags) -LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(common_cpp_flags) -LOCAL_SRC_FILES := $(test_files) -LOCAL_SHARED_LIBRARIES := libziparchive-host liblog libbase +LOCAL_CFLAGS := $(libziparchive_common_c_flags) +LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(libziparchive_common_cpp_flags) +LOCAL_SRC_FILES := $(libziparchive_test_files) +LOCAL_SHARED_LIBRARIES := \ + libziparchive-host \ + liblog \ + libbase \ + LOCAL_STATIC_LIBRARIES := \ + libutils \ libz \ - libutils + include $(BUILD_HOST_NATIVE_TEST) diff --git a/libziparchive/testdata/bad_crc.zip b/libziparchive/testdata/bad_crc.zip new file mode 100644 index 000000000..e12ba07bd Binary files /dev/null and b/libziparchive/testdata/bad_crc.zip differ diff --git a/libziparchive/testdata/large.zip b/libziparchive/testdata/large.zip new file mode 100644 index 000000000..49659c832 Binary files /dev/null and b/libziparchive/testdata/large.zip differ diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc index 07ef6cd19..3b1e972ce 100644 --- a/libziparchive/zip_archive.cc +++ b/libziparchive/zip_archive.cc @@ -36,11 +36,12 @@ #include "log/log.h" #include "utils/Compat.h" #include "utils/FileMap.h" +#include "ziparchive/zip_archive.h" #include "zlib.h" #include "entry_name_utils-inl.h" #include "zip_archive_common.h" -#include "ziparchive/zip_archive.h" +#include "zip_archive_private.h" using android::base::get_unaligned; @@ -134,43 +135,6 @@ static const int32_t kErrorMessageLowerBound = -13; * every page that the Central Directory touches. Easier to tuck a copy * of the string length into the hash table entry. */ -struct ZipArchive { - /* open Zip archive */ - const int fd; - const bool close_file; - - /* mapped central directory area */ - off64_t directory_offset; - android::FileMap directory_map; - - /* number of entries in the Zip archive */ - uint16_t num_entries; - - /* - * We know how many entries are in the Zip archive, so we can have a - * fixed-size hash table. We define a load factor of 0.75 and overallocat - * so the maximum number entries can never be higher than - * ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t. - */ - uint32_t hash_table_size; - 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) {} - - ~ZipArchive() { - if (close_file && fd >= 0) { - close(fd); - } - - free(hash_table); - } -}; /* * Round up to the next highest power of 2. diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h new file mode 100644 index 000000000..ab5236889 --- /dev/null +++ b/libziparchive/zip_archive_private.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_ +#define LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_ + +#include +#include +#include + +#include +#include + +struct ZipArchive { + // open Zip archive + const int fd; + const bool close_file; + + // mapped central directory area + off64_t directory_offset; + android::FileMap directory_map; + + // number of entries in the Zip archive + uint16_t num_entries; + + // We know how many entries are in the Zip archive, so we can have a + // fixed-size hash table. We define a load factor of 0.75 and over + // allocate so the maximum number entries can never be higher than + // ((4 * UINT16_MAX) / 3 + 1) which can safely fit into a uint32_t. + uint32_t hash_table_size; + 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) {} + + ~ZipArchive() { + if (close_file && fd >= 0) { + close(fd); + } + + free(hash_table); + } +}; + +#endif // LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_ diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc new file mode 100644 index 000000000..f61883503 --- /dev/null +++ b/libziparchive/zip_archive_stream_entry.cc @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Read-only stream access to Zip Archive entries. +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_TAG "ZIPARCHIVE" +#include +#include +#include +#include +#include + +#include "zip_archive_private.h" + +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) { + ALOGW("lseek to data at %" PRId64 " failed: %s", data_offset, strerror(errno)); + return false; + } + crc32_ = entry.crc32; + return true; +} + +class ZipArchiveStreamEntryUncompressed : public ZipArchiveStreamEntry { + public: + ZipArchiveStreamEntryUncompressed(ZipArchiveHandle handle) : ZipArchiveStreamEntry(handle) {} + virtual ~ZipArchiveStreamEntryUncompressed() {} + + const std::vector* Read() override; + + bool Verify() override; + + protected: + bool Init(const ZipEntry& entry) override; + + uint32_t length_; + + private: + std::vector data_; + uint32_t computed_crc32_; +}; + +bool ZipArchiveStreamEntryUncompressed::Init(const ZipEntry& entry) { + if (!ZipArchiveStreamEntry::Init(entry)) { + return false; + } + + length_ = entry.uncompressed_length; + + data_.resize(kBufSize); + computed_crc32_ = 0; + + return true; +} + +const std::vector* ZipArchiveStreamEntryUncompressed::Read() { + if (length_ == 0) { + return nullptr; + } + + 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 (errno != 0) { + ALOGE("Error reading from archive fd: %s", strerror(errno)); + } else { + ALOGE("Short read of zip file, possibly corrupted zip?"); + } + length_ = 0; + return nullptr; + } + + if (bytes < data_.size()) { + data_.resize(bytes); + } + computed_crc32_ = crc32(computed_crc32_, data_.data(), data_.size()); + length_ -= bytes; + return &data_; +} + +bool ZipArchiveStreamEntryUncompressed::Verify() { + return length_ == 0 && crc32_ == computed_crc32_; +} + +class ZipArchiveStreamEntryCompressed : public ZipArchiveStreamEntry { + public: + ZipArchiveStreamEntryCompressed(ZipArchiveHandle handle) : ZipArchiveStreamEntry(handle) {} + virtual ~ZipArchiveStreamEntryCompressed(); + + const std::vector* Read() override; + + bool Verify() override; + + protected: + bool Init(const ZipEntry& entry) override; + + private: + bool z_stream_init_ = false; + z_stream z_stream_; + std::vector in_; + std::vector out_; + uint32_t uncompressed_length_; + uint32_t compressed_length_; + uint32_t computed_crc32_; +}; + +// This method is using libz macros with old-style-casts +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +static inline int zlib_inflateInit2(z_stream* stream, int window_bits) { + return inflateInit2(stream, window_bits); +} +#pragma GCC diagnostic pop + +bool ZipArchiveStreamEntryCompressed::Init(const ZipEntry& entry) { + if (!ZipArchiveStreamEntry::Init(entry)) { + return false; + } + + // Initialize the zlib stream struct. + memset(&z_stream_, 0, sizeof(z_stream_)); + z_stream_.zalloc = Z_NULL; + z_stream_.zfree = Z_NULL; + z_stream_.opaque = Z_NULL; + z_stream_.next_in = nullptr; + z_stream_.avail_in = 0; + z_stream_.avail_out = 0; + z_stream_.data_type = Z_UNKNOWN; + + // Use the undocumented "negative window bits" feature to tell zlib + // that there's no zlib header waiting for it. + int zerr = zlib_inflateInit2(&z_stream_, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)", + ZLIB_VERSION); + } else { + ALOGE("Call to inflateInit2 failed (zerr=%d)", zerr); + } + + return false; + } + + z_stream_init_ = true; + + uncompressed_length_ = entry.uncompressed_length; + compressed_length_ = entry.compressed_length; + + out_.resize(kBufSize); + in_.resize(kBufSize); + + computed_crc32_ = 0; + + return true; +} + +ZipArchiveStreamEntryCompressed::~ZipArchiveStreamEntryCompressed() { + if (z_stream_init_) { + inflateEnd(&z_stream_); + z_stream_init_ = false; + } +} + +bool ZipArchiveStreamEntryCompressed::Verify() { + return z_stream_init_ && uncompressed_length_ == 0 && compressed_length_ == 0 && + crc32_ == computed_crc32_; +} + +const std::vector* ZipArchiveStreamEntryCompressed::Read() { + if (z_stream_.avail_out == 0) { + z_stream_.next_out = out_.data(); + z_stream_.avail_out = out_.size();; + } + + while (true) { + if (z_stream_.avail_in == 0) { + if (compressed_length_ == 0) { + return nullptr; + } + 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 (errno != 0) { + ALOGE("Error reading from archive fd: %s", strerror(errno)); + } else { + ALOGE("Short read of zip file, possibly corrupted zip?"); + } + return nullptr; + } + + compressed_length_ -= bytes; + z_stream_.next_in = in_.data(); + z_stream_.avail_in = bytes; + } + + int zerr = inflate(&z_stream_, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGE("inflate zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)", + zerr, z_stream_.next_in, z_stream_.avail_in, + z_stream_.next_out, z_stream_.avail_out); + return nullptr; + } + + if (z_stream_.avail_out == 0) { + uncompressed_length_ -= out_.size(); + computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size()); + return &out_; + } + if (zerr == Z_STREAM_END) { + if (z_stream_.avail_out != 0) { + // Resize the vector down to the actual size of the data. + out_.resize(out_.size() - z_stream_.avail_out); + computed_crc32_ = crc32(computed_crc32_, out_.data(), out_.size()); + uncompressed_length_ -= out_.size(); + return &out_; + } + return nullptr; + } + } + return nullptr; +} + +class ZipArchiveStreamEntryRawCompressed : public ZipArchiveStreamEntryUncompressed { + public: + ZipArchiveStreamEntryRawCompressed(ZipArchiveHandle handle) + : ZipArchiveStreamEntryUncompressed(handle) {} + virtual ~ZipArchiveStreamEntryRawCompressed() {} + + bool Verify() override; + + protected: + bool Init(const ZipEntry& entry) override; +}; + +bool ZipArchiveStreamEntryRawCompressed::Init(const ZipEntry& entry) { + if (!ZipArchiveStreamEntryUncompressed::Init(entry)) { + return false; + } + length_ = entry.compressed_length; + + return true; +} + +bool ZipArchiveStreamEntryRawCompressed::Verify() { + return length_ == 0; +} + +ZipArchiveStreamEntry* ZipArchiveStreamEntry::Create( + ZipArchiveHandle handle, const ZipEntry& entry) { + ZipArchiveStreamEntry* stream = nullptr; + if (entry.method != kCompressStored) { + stream = new ZipArchiveStreamEntryCompressed(handle); + } else { + stream = new ZipArchiveStreamEntryUncompressed(handle); + } + if (stream && !stream->Init(entry)) { + delete stream; + stream = nullptr; + } + + return stream; +} + +ZipArchiveStreamEntry* ZipArchiveStreamEntry::CreateRaw( + ZipArchiveHandle handle, const ZipEntry& entry) { + ZipArchiveStreamEntry* stream = nullptr; + if (entry.method == kCompressStored) { + // Not compressed, don't need to do anything special. + stream = new ZipArchiveStreamEntryUncompressed(handle); + } else { + stream = new ZipArchiveStreamEntryRawCompressed(handle); + } + if (stream && !stream->Init(entry)) { + delete stream; + stream = nullptr; + } + return stream; +} diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc index cb0f41050..d426dc4f4 100644 --- a/libziparchive/zip_archive_test.cc +++ b/libziparchive/zip_archive_test.cc @@ -14,54 +14,49 @@ * limitations under the License. */ -#include "ziparchive/zip_archive.h" - #include #include #include #include +#include #include + #include #include #include +#include +#include static std::string test_data_dir; 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 uint8_t kATxtContents[] = { +static const std::vector kATxtContents { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n' }; -static const uint8_t kBTxtContents[] = { +static const std::vector kATxtContentsCompressed { + 'K', 'L', 'J', 'N', 'I', 'M', 'K', 207, 'H', + 132, 210, '\\', '\0' +}; + +static const std::vector kBTxtContents { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\n' }; -static const uint16_t kATxtNameLength = 5; -static const uint16_t kBTxtNameLength = 5; -static const uint16_t kNonexistentTxtNameLength = 15; -static const uint16_t kEmptyTxtNameLength = 9; - -static const uint8_t kATxtName[kATxtNameLength] = { - 'a', '.', 't', 'x', 't' -}; - -static const uint8_t kBTxtName[kBTxtNameLength] = { - 'b', '.', 't', 'x', 't' -}; - -static const uint8_t kNonexistentTxtName[kNonexistentTxtNameLength] = { - 'n', 'o', 'n', 'e', 'x', 'i', 's', 't', 'e', 'n', 't', '.', 't', 'x' ,'t' -}; - -static const uint8_t kEmptyTxtName[kEmptyTxtNameLength] = { - 'e', 'm', 'p', 't', 'y', '.', 't', 'x', 't' -}; +static const std::string kATxtName("a.txt"); +static const std::string kBTxtName("b.txt"); +static const std::string kNonexistentTxtName("nonexistent.txt"); +static const std::string kEmptyTxtName("empty.txt"); +static const std::string kLargeCompressTxtName("compress.txt"); +static const std::string kLargeUncompressTxtName("uncompress.txt"); static int32_t OpenArchiveWrapper(const std::string& name, ZipArchiveHandle* handle) { @@ -75,6 +70,11 @@ static void AssertNameEquals(const std::string& name_str, ASSERT_EQ(0, memcmp(name_str.c_str(), name.name, name.name_length)); } +static void SetZipString(ZipString* zip_str, const std::string& str) { + zip_str->name = reinterpret_cast(str.c_str()); + zip_str->name_length = str.size(); +} + TEST(ziparchive, Open) { ZipArchiveHandle handle; ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); @@ -115,7 +115,7 @@ TEST(ziparchive, Iteration) { ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle)); void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL)); + ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, nullptr)); ZipEntry data; ZipString name; @@ -152,7 +152,7 @@ TEST(ziparchive, IterationWithPrefix) { void* iteration_cookie; ZipString prefix("b/"); - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, NULL)); + ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, &prefix, nullptr)); ZipEntry data; ZipString name; @@ -181,7 +181,7 @@ TEST(ziparchive, IterationWithSuffix) { void* iteration_cookie; ZipString suffix(".txt"); - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, &suffix)); + ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, &suffix)); ZipEntry data; ZipString name; @@ -262,8 +262,7 @@ TEST(ziparchive, FindEntry) { ZipEntry data; ZipString name; - name.name = kATxtName; - name.name_length = kATxtNameLength; + SetZipString(&name, kATxtName); ASSERT_EQ(0, FindEntry(handle, name, &data)); // Known facts about a.txt, from zipinfo -v. @@ -276,8 +275,7 @@ TEST(ziparchive, FindEntry) { // An entry that doesn't exist. Should be a negative return code. ZipString absent_name; - absent_name.name = kNonexistentTxtName; - absent_name.name_length = kNonexistentTxtNameLength; + SetZipString(&absent_name, kNonexistentTxtName); ASSERT_LT(FindEntry(handle, absent_name, &data), 0); CloseArchive(handle); @@ -288,7 +286,7 @@ TEST(ziparchive, TestInvalidDeclaredLength) { ASSERT_EQ(0, OpenArchiveWrapper("declaredlength.zip", &handle)); void* iteration_cookie; - ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, NULL, NULL)); + ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, nullptr, nullptr)); ZipString name; ZipEntry data; @@ -306,26 +304,24 @@ TEST(ziparchive, ExtractToMemory) { // An entry that's deflated. ZipEntry data; ZipString a_name; - a_name.name = kATxtName; - a_name.name_length = kATxtNameLength; + SetZipString(&a_name, kATxtName); ASSERT_EQ(0, FindEntry(handle, a_name, &data)); const uint32_t a_size = data.uncompressed_length; - ASSERT_EQ(a_size, sizeof(kATxtContents)); + ASSERT_EQ(a_size, kATxtContents.size()); uint8_t* buffer = new uint8_t[a_size]; ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, a_size)); - ASSERT_EQ(0, memcmp(buffer, kATxtContents, a_size)); + ASSERT_EQ(0, memcmp(buffer, kATxtContents.data(), a_size)); delete[] buffer; // An entry that's stored. ZipString b_name; - b_name.name = kBTxtName; - b_name.name_length = kBTxtNameLength; + SetZipString(&b_name, kBTxtName); ASSERT_EQ(0, FindEntry(handle, b_name, &data)); const uint32_t b_size = data.uncompressed_length; - ASSERT_EQ(b_size, sizeof(kBTxtContents)); + ASSERT_EQ(b_size, kBTxtContents.size()); buffer = new uint8_t[b_size]; ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, b_size)); - ASSERT_EQ(0, memcmp(buffer, kBTxtContents, b_size)); + ASSERT_EQ(0, memcmp(buffer, kBTxtContents.data(), b_size)); delete[] buffer; CloseArchive(handle); @@ -374,8 +370,7 @@ static const uint16_t kAbZip[] = { 0x0100, 0x4c00, 0x0000, 0x5b00, 0x0001, 0x0000, 0x0000 }; -static const uint8_t kAbTxtName[] = { 'a', 'b', '.', 't', 'x', 't' }; -static const uint16_t kAbTxtNameLength = sizeof(kAbTxtName); +static const std::string kAbTxtName("ab.txt"); static const size_t kAbUncompressedSize = 270216; static int make_temporary_file(const char* file_name_pattern) { @@ -405,8 +400,7 @@ TEST(ziparchive, EmptyEntries) { ZipEntry entry; ZipString empty_name; - empty_name.name = kEmptyTxtName; - empty_name.name_length = kEmptyTxtNameLength; + SetZipString(&empty_name, kEmptyTxtName); ASSERT_EQ(0, FindEntry(handle, empty_name, &entry)); ASSERT_EQ(static_cast(0), entry.uncompressed_length); uint8_t buffer[1]; @@ -436,8 +430,7 @@ TEST(ziparchive, EntryLargerThan32K) { ZipEntry entry; ZipString ab_name; - ab_name.name = kAbTxtName; - ab_name.name_length = kAbTxtNameLength; + SetZipString(&ab_name, kAbTxtName); ASSERT_EQ(0, FindEntry(handle, ab_name, &entry)); ASSERT_EQ(kAbUncompressedSize, entry.uncompressed_length); @@ -504,8 +497,7 @@ TEST(ziparchive, ExtractToFile) { ZipEntry entry; ZipString name; - name.name = kATxtName; - name.name_length = kATxtNameLength; + SetZipString(&name, kATxtName); ASSERT_EQ(0, FindEntry(handle, name, &entry)); ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, fd)); @@ -521,22 +513,131 @@ TEST(ziparchive, ExtractToFile) { ASSERT_EQ(static_cast(entry.uncompressed_length), TEMP_FAILURE_RETRY( read(fd, &uncompressed_data[0], entry.uncompressed_length))); - ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents, - sizeof(kATxtContents))); + ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents.data(), + kATxtContents.size())); // Assert that the total length of the file is sane - ASSERT_EQ(data_size + static_cast(sizeof(kATxtContents)), + ASSERT_EQ(data_size + static_cast(kATxtContents.size()), lseek64(fd, 0, SEEK_END)); close(fd); } +static void ZipArchiveStreamTest( + ZipArchiveHandle& handle, const std::string& entry_name, bool raw, + bool verified, ZipEntry* entry, std::vector* read_data) { + ZipString name; + SetZipString(&name, entry_name); + ASSERT_EQ(0, FindEntry(handle, name, entry)); + std::unique_ptr stream; + if (raw) { + stream.reset(ZipArchiveStreamEntry::CreateRaw(handle, *entry)); + if (entry->method == kCompressStored) { + read_data->resize(entry->uncompressed_length); + } else { + read_data->resize(entry->compressed_length); + } + } else { + stream.reset(ZipArchiveStreamEntry::Create(handle, *entry)); + read_data->resize(entry->uncompressed_length); + } + uint8_t* read_data_ptr = read_data->data(); + ASSERT_TRUE(stream.get() != nullptr); + const std::vector* data; + uint64_t total_size = 0; + while ((data = stream->Read()) != nullptr) { + total_size += data->size(); + memcpy(read_data_ptr, data->data(), data->size()); + read_data_ptr += data->size(); + } + ASSERT_EQ(verified, stream->Verify()); + ASSERT_EQ(total_size, read_data->size()); +} + +static void ZipArchiveStreamTestUsingContents( + const std::string& zip_file, const std::string& entry_name, + const std::vector& contents, bool raw) { + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle)); + + ZipEntry entry; + std::vector read_data; + ZipArchiveStreamTest(handle, entry_name, raw, true, &entry, &read_data); + + ASSERT_EQ(contents.size(), read_data.size()); + ASSERT_TRUE(memcmp(read_data.data(), contents.data(), read_data.size()) == 0); + + CloseArchive(handle); +} + +static void ZipArchiveStreamTestUsingMemory(const std::string& zip_file, const std::string& entry_name) { + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle)); + + ZipEntry entry; + std::vector read_data; + ZipArchiveStreamTest(handle, entry_name, false, true, &entry, &read_data); + + std::vector cmp_data(entry.uncompressed_length); + ASSERT_EQ(entry.uncompressed_length, read_data.size()); + ASSERT_EQ(0, ExtractToMemory(handle, &entry, cmp_data.data(), cmp_data.size())); + ASSERT_TRUE(memcmp(read_data.data(), cmp_data.data(), read_data.size()) == 0); + + CloseArchive(handle); +} + +TEST(ziparchive, StreamCompressed) { + ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContents, false); +} + +TEST(ziparchive, StreamUncompressed) { + ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, false); +} + +TEST(ziparchive, StreamRawCompressed) { + ZipArchiveStreamTestUsingContents(kValidZip, kATxtName, kATxtContentsCompressed, true); +} + +TEST(ziparchive, StreamRawUncompressed) { + ZipArchiveStreamTestUsingContents(kValidZip, kBTxtName, kBTxtContents, true); +} + +TEST(ziparchive, StreamLargeCompressed) { + ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeCompressTxtName); +} + +TEST(ziparchive, StreamLargeUncompressed) { + ZipArchiveStreamTestUsingMemory(kLargeZip, kLargeUncompressTxtName); +} + +TEST(ziparchive, StreamCompressedBadCrc) { + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle)); + + ZipEntry entry; + std::vector read_data; + ZipArchiveStreamTest(handle, kATxtName, false, false, &entry, &read_data); + + CloseArchive(handle); +} + +TEST(ziparchive, StreamUncompressedBadCrc) { + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveWrapper(kBadCrcZip, &handle)); + + ZipEntry entry; + std::vector read_data; + ZipArchiveStreamTest(handle, kBTxtName, false, false, &entry, &read_data); + + CloseArchive(handle); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); static struct option options[] = { - { "test_data_dir", required_argument, NULL, 't' }, - { NULL, 0, NULL, 0 } + { "test_data_dir", required_argument, nullptr, 't' }, + { nullptr, 0, nullptr, 0 } }; while (true) { @@ -557,9 +658,15 @@ int main(int argc, char** argv) { } if (test_data_dir[0] != '/') { - printf("Test data must be an absolute path, was %s\n\n", - test_data_dir.c_str()); - return -2; + std::vector cwd_buffer(1024); + const char* cwd = getcwd(cwd_buffer.data(), cwd_buffer.size() - 1); + if (cwd == nullptr) { + printf("Cannot get current working directory, use an absolute path instead, was %s\n\n", + test_data_dir.c_str()); + return -2; + } + test_data_dir = '/' + test_data_dir; + test_data_dir = cwd + test_data_dir; } return RUN_ALL_TESTS();