Add a ZipArchiveStreamEntry class.

This allows someone to stream the data out of a zip archive
instead of extracting to a file or to memory.

Included in this change is a small cleanup of the makefile.

Change-Id: I8b679a679c3502ff4ea0bc4f9e918303657fa424
This commit is contained in:
Christopher Ferris 2015-11-10 14:55:12 -08:00
parent 047597b3fc
commit e6884ce56f
8 changed files with 626 additions and 117 deletions

View File

@ -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 <vector>
#include <ziparchive/zip_archive.h>
class ZipArchiveStreamEntry {
public:
virtual ~ZipArchiveStreamEntry() {}
virtual const std::vector<uint8_t>* 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_

View File

@ -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)

BIN
libziparchive/testdata/bad_crc.zip vendored Normal file

Binary file not shown.

BIN
libziparchive/testdata/large.zip vendored Normal file

Binary file not shown.

View File

@ -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.

View File

@ -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 <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <utils/FileMap.h>
#include <ziparchive/zip_archive.h>
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_

View File

@ -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 <errno.h>
#include <inttypes.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <vector>
#define LOG_TAG "ZIPARCHIVE"
#include <android-base/file.h>
#include <log/log.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
#include <zlib.h>
#include "zip_archive_private.h"
static constexpr size_t kBufSize = 65535;
bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) {
ZipArchive* archive = reinterpret_cast<ZipArchive*>(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<uint8_t>* Read() override;
bool Verify() override;
protected:
bool Init(const ZipEntry& entry) override;
uint32_t length_;
private:
std::vector<uint8_t> 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<uint8_t>* ZipArchiveStreamEntryUncompressed::Read() {
if (length_ == 0) {
return nullptr;
}
size_t bytes = (length_ > data_.size()) ? data_.size() : length_;
ZipArchive* archive = reinterpret_cast<ZipArchive*>(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<uint8_t>* Read() override;
bool Verify() override;
protected:
bool Init(const ZipEntry& entry) override;
private:
bool z_stream_init_ = false;
z_stream z_stream_;
std::vector<uint8_t> in_;
std::vector<uint8_t> 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<uint8_t>* 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<ZipArchive*>(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;
}

View File

@ -14,54 +14,49 @@
* limitations under the License.
*/
#include "ziparchive/zip_archive.h"
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#include <android-base/file.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_archive.h>
#include <ziparchive/zip_archive_stream_entry.h>
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<uint8_t> 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<uint8_t> kATxtContentsCompressed {
'K', 'L', 'J', 'N', 'I', 'M', 'K', 207, 'H',
132, 210, '\\', '\0'
};
static const std::vector<uint8_t> 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<const uint8_t*>(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<uint32_t>(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<ssize_t>(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<ssize_t>(sizeof(kATxtContents)),
ASSERT_EQ(data_size + static_cast<ssize_t>(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<uint8_t>* read_data) {
ZipString name;
SetZipString(&name, entry_name);
ASSERT_EQ(0, FindEntry(handle, name, entry));
std::unique_ptr<ZipArchiveStreamEntry> 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<uint8_t>* 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<uint8_t>& contents, bool raw) {
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(zip_file, &handle));
ZipEntry entry;
std::vector<uint8_t> 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<uint8_t> read_data;
ZipArchiveStreamTest(handle, entry_name, false, true, &entry, &read_data);
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<char> 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();