Implement ZipWriter for quickly writing ZipFiles.

The ZipWriter implementation exposes a stateful interface that allows
bytes of data to be streamed in as they arrive. ZipEntries can be
compressed and/or aligned on a 32-bit boundary for mmapping at runtime.

Bug:22489826
Change-Id: Ifdabcde3ea5747dd4bcf0c4ac0eb5c394277443c
This commit is contained in:
Adam Lesinski 2015-10-05 18:16:18 -07:00
parent d0c54deb1d
commit 736bdcdf1b
6 changed files with 732 additions and 158 deletions

View File

@ -0,0 +1,141 @@
/*
* 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.
*/
#ifndef LIBZIPARCHIVE_ZIPWRITER_H_
#define LIBZIPARCHIVE_ZIPWRITER_H_
#include "base/macros.h"
#include <utils/Compat.h>
#include <cstdio>
#include <string>
#include <ctime>
#include <vector>
/**
* Writes a Zip file via a stateful interface.
*
* Example:
*
* FILE* file = fopen("path/to/zip.zip", "wb");
*
* ZipWriter writer(file);
*
* writer.StartEntry("test.txt", ZipWriter::kCompress | ZipWriter::kAlign);
* writer.WriteBytes(buffer, bufferLen);
* writer.WriteBytes(buffer2, bufferLen2);
* writer.FinishEntry();
*
* writer.StartEntry("empty.txt", 0);
* writer.FinishEntry();
*
* writer.Finish();
*
* fclose(file);
*/
class ZipWriter {
public:
enum {
/**
* Flag to compress the zip entry using deflate.
*/
kCompress = 0x01,
/**
* Flag to align the zip entry data on a 32bit boundary. Useful for
* mmapping the data at runtime.
*/
kAlign32 = 0x02,
};
static const char* ErrorCodeString(int32_t error_code);
/**
* Create a ZipWriter that will write into a FILE stream. The file should be opened with
* open mode of "wb" or "w+b". ZipWriter does not take ownership of the file stream. The
* caller is responsible for closing the file.
*/
explicit ZipWriter(FILE* f);
// Move constructor.
ZipWriter(ZipWriter&& zipWriter);
// Move assignment.
ZipWriter& operator=(ZipWriter&& zipWriter);
/**
* Starts a new zip entry with the given path and flags.
* Flags can be a bitwise OR of ZipWriter::kCompress and ZipWriter::kAlign.
* Subsequent calls to WriteBytes(const void*, size_t) will add data to this entry.
* Returns 0 on success, and an error value < 0 on failure.
*/
int32_t StartEntry(const char* path, size_t flags);
/**
* Same as StartEntry(const char*, size_t), but sets a last modified time for the entry.
*/
int32_t StartEntryWithTime(const char* path, size_t flags, time_t time);
/**
* Writes bytes to the zip file for the previously started zip entry.
* Returns 0 on success, and an error value < 0 on failure.
*/
int32_t WriteBytes(const void* data, size_t len);
/**
* Finish a zip entry started with StartEntry(const char*, size_t) or
* StartEntryWithTime(const char*, size_t, time_t). This must be called before
* any new zip entries are started, or before Finish() is called.
* Returns 0 on success, and an error value < 0 on failure.
*/
int32_t FinishEntry();
/**
* Writes the Central Directory Headers and flushes the zip file stream.
* Returns 0 on success, and an error value < 0 on failure.
*/
int32_t Finish();
private:
DISALLOW_COPY_AND_ASSIGN(ZipWriter);
int32_t HandleError(int32_t error_code);
struct FileInfo {
std::string path;
uint16_t compression_method;
uint32_t crc32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t last_mod_time;
uint16_t last_mod_date;
uint32_t local_file_header_offset;
};
enum class State {
kWritingZip,
kWritingEntry,
kDone,
kError,
};
FILE* file_;
off64_t current_offset_;
State state_;
std::vector<FileInfo> files_;
};
#endif /* LIBZIPARCHIVE_ZIPWRITER_H_ */

View File

@ -15,7 +15,8 @@
LOCAL_PATH := $(call my-dir)
source_files := zip_archive.cc
source_files := zip_archive.cc zip_writer.cc
test_files := zip_archive_test.cc zip_writer_test.cc entry_name_utils_test.cc
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
@ -34,6 +35,11 @@ LOCAL_STATIC_LIBRARIES := libz libutils libbase
LOCAL_MODULE:= libziparchive-host
LOCAL_CFLAGS := -Werror
LOCAL_CFLAGS_windows := -mno-ms-bitfields
# Incorrectly warns when C++11 empty brace {} initializer is used.
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489
LOCAL_CFLAGS_windows += -Wno-missing-field-initializers
LOCAL_MULTILIB := both
LOCAL_MODULE_HOST_OS := darwin linux windows
include $(BUILD_HOST_STATIC_LIBRARY)
@ -53,7 +59,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE := ziparchive-tests
LOCAL_CPP_EXTENSION := .cc
LOCAL_CFLAGS := -Werror
LOCAL_SRC_FILES := zip_archive_test.cc entry_name_utils_test.cc
LOCAL_SRC_FILES := $(test_files)
LOCAL_SHARED_LIBRARIES := liblog libbase
LOCAL_STATIC_LIBRARIES := libziparchive libz libutils
include $(BUILD_NATIVE_TEST)
@ -64,7 +70,7 @@ LOCAL_CPP_EXTENSION := .cc
LOCAL_CFLAGS += \
-Werror \
-Wno-unnamed-type-template-args
LOCAL_SRC_FILES := zip_archive_test.cc entry_name_utils_test.cc
LOCAL_SRC_FILES := $(test_files)
LOCAL_SHARED_LIBRARIES := libziparchive-host liblog libbase
LOCAL_STATIC_LIBRARIES := \
libz \

View File

@ -39,6 +39,7 @@
#include "zlib.h"
#include "entry_name_utils-inl.h"
#include "zip_archive_common.h"
#include "ziparchive/zip_archive.h"
using android::base::get_unaligned;
@ -49,161 +50,6 @@ using android::base::get_unaligned;
#define O_BINARY 0
#endif
// 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
// number of entries in the archive and the offset to the central
// directory of the offset.
struct EocdRecord {
static const uint32_t kSignature = 0x06054b50;
// End of central directory signature, should always be
// |kSignature|.
uint32_t eocd_signature;
// The number of the current "disk", i.e, the "disk" that this
// central directory is on.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that disk_num == 1.
uint16_t disk_num;
// The disk where the central directory starts.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that cd_start_disk == 1.
uint16_t cd_start_disk;
// The number of central directory records on this disk.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that num_records_on_disk == num_records.
uint16_t num_records_on_disk;
// The total number of central directory records.
uint16_t num_records;
// The size of the central directory (in bytes).
uint32_t cd_size;
// The offset of the start of the central directory, relative
// to the start of the file.
uint32_t cd_start_offset;
// Length of the central directory comment.
uint16_t comment_length;
private:
EocdRecord() = default;
DISALLOW_COPY_AND_ASSIGN(EocdRecord);
} __attribute__((packed));
// A structure representing the fixed length fields for a single
// record in the central directory of the archive. In addition to
// the fixed length fields listed here, each central directory
// record contains a variable length "file_name" and "extra_field"
// whose lengths are given by |file_name_length| and |extra_field_length|
// respectively.
struct CentralDirectoryRecord {
static const uint32_t kSignature = 0x02014b50;
// The start of record signature. Must be |kSignature|.
uint32_t record_signature;
// Tool version. Ignored by this implementation.
uint16_t version_made_by;
// Tool version. Ignored by this implementation.
uint16_t version_needed;
// The "general purpose bit flags" for this entry. The only
// flag value that we currently check for is the "data descriptor"
// flag.
uint16_t gpb_flags;
// The compression method for this entry, one of |kCompressStored|
// and |kCompressDeflated|.
uint16_t compression_method;
// The file modification time and date for this entry.
uint16_t last_mod_time;
uint16_t last_mod_date;
// The CRC-32 checksum for this entry.
uint32_t crc32;
// The compressed size (in bytes) of this entry.
uint32_t compressed_size;
// The uncompressed size (in bytes) of this entry.
uint32_t uncompressed_size;
// The length of the entry file name in bytes. The file name
// will appear immediately after this record.
uint16_t file_name_length;
// The length of the extra field info (in bytes). This data
// will appear immediately after the entry file name.
uint16_t extra_field_length;
// The length of the entry comment (in bytes). This data will
// appear immediately after the extra field.
uint16_t comment_length;
// The start disk for this entry. Ignored by this implementation).
uint16_t file_start_disk;
// File attributes. Ignored by this implementation.
uint16_t internal_file_attributes;
// File attributes. Ignored by this implementation.
uint32_t external_file_attributes;
// The offset to the local file header for this entry, from the
// beginning of this archive.
uint32_t local_file_header_offset;
private:
CentralDirectoryRecord() = default;
DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
} __attribute__((packed));
// The local file header for a given entry. This duplicates information
// present in the central directory of the archive. It is an error for
// the information here to be different from the central directory
// information for a given entry.
struct LocalFileHeader {
static const uint32_t kSignature = 0x04034b50;
// The local file header signature, must be |kSignature|.
uint32_t lfh_signature;
// Tool version. Ignored by this implementation.
uint16_t version_needed;
// The "general purpose bit flags" for this entry. The only
// flag value that we currently check for is the "data descriptor"
// flag.
uint16_t gpb_flags;
// The compression method for this entry, one of |kCompressStored|
// and |kCompressDeflated|.
uint16_t compression_method;
// The file modification time and date for this entry.
uint16_t last_mod_time;
uint16_t last_mod_date;
// The CRC-32 checksum for this entry.
uint32_t crc32;
// The compressed size (in bytes) of this entry.
uint32_t compressed_size;
// The uncompressed size (in bytes) of this entry.
uint32_t uncompressed_size;
// The length of the entry file name in bytes. The file name
// will appear immediately after this record.
uint16_t file_name_length;
// The length of the extra field info (in bytes). This data
// will appear immediately after the entry file name.
uint16_t extra_field_length;
private:
LocalFileHeader() = default;
DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
} __attribute__((packed));
struct DataDescriptor {
// The *optional* data descriptor start signature.
static const uint32_t kOptSignature = 0x08074b50;
// CRC-32 checksum of the entry.
uint32_t crc32;
// Compressed size of the entry.
uint32_t compressed_size;
// Uncompressed size of the entry.
uint32_t uncompressed_size;
private:
DataDescriptor() = default;
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
} __attribute__((packed));
static const uint32_t kGPBDDFlagMask = 0x0008; // mask value that signifies that the entry has a DD
// The maximum size of a central directory or a file
// comment in bytes.
static const uint32_t kMaxCommentLen = 65535;
// The maximum number of bytes to scan backwards for the EOCD start.
static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);

View File

@ -0,0 +1,179 @@
/*
* 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.
*/
#ifndef LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_
#define LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_
#include "base/macros.h"
#include <inttypes.h>
// 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
// number of entries in the archive and the offset to the central
// directory of the offset.
struct EocdRecord {
static const uint32_t kSignature = 0x06054b50;
// End of central directory signature, should always be
// |kSignature|.
uint32_t eocd_signature;
// The number of the current "disk", i.e, the "disk" that this
// central directory is on.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that disk_num == 1.
uint16_t disk_num;
// The disk where the central directory starts.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that cd_start_disk == 1.
uint16_t cd_start_disk;
// The number of central directory records on this disk.
//
// This implementation assumes that each archive spans a single
// disk only. i.e, that num_records_on_disk == num_records.
uint16_t num_records_on_disk;
// The total number of central directory records.
uint16_t num_records;
// The size of the central directory (in bytes).
uint32_t cd_size;
// The offset of the start of the central directory, relative
// to the start of the file.
uint32_t cd_start_offset;
// Length of the central directory comment.
uint16_t comment_length;
private:
EocdRecord() = default;
DISALLOW_COPY_AND_ASSIGN(EocdRecord);
} __attribute__((packed));
// A structure representing the fixed length fields for a single
// record in the central directory of the archive. In addition to
// the fixed length fields listed here, each central directory
// record contains a variable length "file_name" and "extra_field"
// whose lengths are given by |file_name_length| and |extra_field_length|
// respectively.
struct CentralDirectoryRecord {
static const uint32_t kSignature = 0x02014b50;
// The start of record signature. Must be |kSignature|.
uint32_t record_signature;
// Tool version. Ignored by this implementation.
uint16_t version_made_by;
// Tool version. Ignored by this implementation.
uint16_t version_needed;
// The "general purpose bit flags" for this entry. The only
// flag value that we currently check for is the "data descriptor"
// flag.
uint16_t gpb_flags;
// The compression method for this entry, one of |kCompressStored|
// and |kCompressDeflated|.
uint16_t compression_method;
// The file modification time and date for this entry.
uint16_t last_mod_time;
uint16_t last_mod_date;
// The CRC-32 checksum for this entry.
uint32_t crc32;
// The compressed size (in bytes) of this entry.
uint32_t compressed_size;
// The uncompressed size (in bytes) of this entry.
uint32_t uncompressed_size;
// The length of the entry file name in bytes. The file name
// will appear immediately after this record.
uint16_t file_name_length;
// The length of the extra field info (in bytes). This data
// will appear immediately after the entry file name.
uint16_t extra_field_length;
// The length of the entry comment (in bytes). This data will
// appear immediately after the extra field.
uint16_t comment_length;
// The start disk for this entry. Ignored by this implementation).
uint16_t file_start_disk;
// File attributes. Ignored by this implementation.
uint16_t internal_file_attributes;
// File attributes. Ignored by this implementation.
uint32_t external_file_attributes;
// The offset to the local file header for this entry, from the
// beginning of this archive.
uint32_t local_file_header_offset;
private:
CentralDirectoryRecord() = default;
DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
} __attribute__((packed));
// The local file header for a given entry. This duplicates information
// present in the central directory of the archive. It is an error for
// the information here to be different from the central directory
// information for a given entry.
struct LocalFileHeader {
static const uint32_t kSignature = 0x04034b50;
// The local file header signature, must be |kSignature|.
uint32_t lfh_signature;
// Tool version. Ignored by this implementation.
uint16_t version_needed;
// The "general purpose bit flags" for this entry. The only
// flag value that we currently check for is the "data descriptor"
// flag.
uint16_t gpb_flags;
// The compression method for this entry, one of |kCompressStored|
// and |kCompressDeflated|.
uint16_t compression_method;
// The file modification time and date for this entry.
uint16_t last_mod_time;
uint16_t last_mod_date;
// The CRC-32 checksum for this entry.
uint32_t crc32;
// The compressed size (in bytes) of this entry.
uint32_t compressed_size;
// The uncompressed size (in bytes) of this entry.
uint32_t uncompressed_size;
// The length of the entry file name in bytes. The file name
// will appear immediately after this record.
uint16_t file_name_length;
// The length of the extra field info (in bytes). This data
// will appear immediately after the entry file name.
uint16_t extra_field_length;
private:
LocalFileHeader() = default;
DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
} __attribute__((packed));
struct DataDescriptor {
// The *optional* data descriptor start signature.
static const uint32_t kOptSignature = 0x08074b50;
// CRC-32 checksum of the entry.
uint32_t crc32;
// Compressed size of the entry.
uint32_t compressed_size;
// Uncompressed size of the entry.
uint32_t uncompressed_size;
private:
DataDescriptor() = default;
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
} __attribute__((packed));
// mask value that signifies that the entry has a DD
static const uint32_t kGPBDDFlagMask = 0x0008;
// The maximum size of a central directory or a file
// comment in bytes.
static const uint32_t kMaxCommentLen = 65535;
#endif /* LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_ */

262
libziparchive/zip_writer.cc Normal file
View File

@ -0,0 +1,262 @@
/*
* 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.
*/
#include "entry_name_utils-inl.h"
#include "zip_archive_common.h"
#include "ziparchive/zip_writer.h"
#include <cassert>
#include <cstdio>
#include <memory>
#include <zlib.h>
/* Zip compression methods we support */
enum {
kCompressStored = 0, // no compression
kCompressDeflated = 8, // standard deflate
};
// No error, operation completed successfully.
static const int32_t kNoError = 0;
// The ZipWriter is in a bad state.
static const int32_t kInvalidState = -1;
// There was an IO error while writing to disk.
static const int32_t kIoError = -2;
// The zip entry name was invalid.
static const int32_t kInvalidEntryName = -3;
static const char* sErrorCodes[] = {
"Invalid state",
"IO error",
"Invalid entry name",
};
const char* ZipWriter::ErrorCodeString(int32_t error_code) {
if (error_code < 0 && (-error_code) < static_cast<int32_t>(arraysize(sErrorCodes))) {
return sErrorCodes[-error_code];
}
return nullptr;
}
ZipWriter::ZipWriter(FILE* f) : file_(f), current_offset_(0), state_(State::kWritingZip) {
}
ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_),
current_offset_(writer.current_offset_),
state_(writer.state_),
files_(std::move(writer.files_)) {
writer.file_ = nullptr;
writer.state_ = State::kError;
}
ZipWriter& ZipWriter::operator=(ZipWriter&& writer) {
file_ = writer.file_;
current_offset_ = writer.current_offset_;
state_ = writer.state_;
files_ = std::move(writer.files_);
writer.file_ = nullptr;
writer.state_ = State::kError;
return *this;
}
int32_t ZipWriter::HandleError(int32_t error_code) {
state_ = State::kError;
return error_code;
}
int32_t ZipWriter::StartEntry(const char* path, size_t flags) {
return StartEntryWithTime(path, flags, time_t());
}
static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) {
/* round up to an even number of seconds */
when = static_cast<time_t>((static_cast<unsigned long>(when) + 1) & (~1));
struct tm* ptm;
#if !defined(_WIN32)
struct tm tm_result;
ptm = localtime_r(&when, &tm_result);
#else
ptm = localtime(&when);
#endif
int year = ptm->tm_year;
if (year < 80) {
year = 80;
}
*out_date = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
*out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
}
int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) {
if (state_ != State::kWritingZip) {
return kInvalidState;
}
FileInfo fileInfo = {};
fileInfo.path = std::string(path);
fileInfo.local_file_header_offset = current_offset_;
if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(fileInfo.path.data()),
fileInfo.path.size())) {
return kInvalidEntryName;
}
LocalFileHeader header = {};
header.lfh_signature = LocalFileHeader::kSignature;
// Set this flag to denote that a DataDescriptor struct will appear after the data,
// containing the crc and size fields.
header.gpb_flags |= kGPBDDFlagMask;
// For now, ignore the ZipWriter::kCompress flag.
fileInfo.compression_method = kCompressStored;
header.compression_method = fileInfo.compression_method;
ExtractTimeAndDate(time, &fileInfo.last_mod_time, &fileInfo.last_mod_date);
header.last_mod_time = fileInfo.last_mod_time;
header.last_mod_date = fileInfo.last_mod_date;
header.file_name_length = fileInfo.path.size();
off64_t offset = current_offset_ + sizeof(header) + fileInfo.path.size();
if ((flags & ZipWriter::kAlign32) && (offset & 0x03)) {
// Pad the extra field so the data will be aligned.
uint16_t padding = 4 - (offset % 4);
header.extra_field_length = padding;
offset += padding;
}
if (fwrite(&header, sizeof(header), 1, file_) != 1) {
return HandleError(kIoError);
}
if (fwrite(path, sizeof(*path), fileInfo.path.size(), file_) != fileInfo.path.size()) {
return HandleError(kIoError);
}
if (fwrite("\0\0\0", 1, header.extra_field_length, file_) != header.extra_field_length) {
return HandleError(kIoError);
}
files_.emplace_back(std::move(fileInfo));
current_offset_ = offset;
state_ = State::kWritingEntry;
return kNoError;
}
int32_t ZipWriter::WriteBytes(const void* data, size_t len) {
if (state_ != State::kWritingEntry) {
return HandleError(kInvalidState);
}
FileInfo& currentFile = files_.back();
if (currentFile.compression_method & kCompressDeflated) {
// TODO(adamlesinski): Implement compression using zlib deflate.
assert(false);
} else {
if (fwrite(data, 1, len, file_) != len) {
return HandleError(kIoError);
}
currentFile.crc32 = crc32(currentFile.crc32, reinterpret_cast<const Bytef*>(data), len);
currentFile.compressed_size += len;
current_offset_ += len;
}
currentFile.uncompressed_size += len;
return kNoError;
}
int32_t ZipWriter::FinishEntry() {
if (state_ != State::kWritingEntry) {
return kInvalidState;
}
const uint32_t sig = DataDescriptor::kOptSignature;
if (fwrite(&sig, sizeof(sig), 1, file_) != 1) {
state_ = State::kError;
return kIoError;
}
FileInfo& currentFile = files_.back();
DataDescriptor dd = {};
dd.crc32 = currentFile.crc32;
dd.compressed_size = currentFile.compressed_size;
dd.uncompressed_size = currentFile.uncompressed_size;
if (fwrite(&dd, sizeof(dd), 1, file_) != 1) {
return HandleError(kIoError);
}
current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd);
state_ = State::kWritingZip;
return kNoError;
}
int32_t ZipWriter::Finish() {
if (state_ != State::kWritingZip) {
return kInvalidState;
}
off64_t startOfCdr = current_offset_;
for (FileInfo& file : files_) {
CentralDirectoryRecord cdr = {};
cdr.record_signature = CentralDirectoryRecord::kSignature;
cdr.gpb_flags |= kGPBDDFlagMask;
cdr.compression_method = file.compression_method;
cdr.last_mod_time = file.last_mod_time;
cdr.last_mod_date = file.last_mod_date;
cdr.crc32 = file.crc32;
cdr.compressed_size = file.compressed_size;
cdr.uncompressed_size = file.uncompressed_size;
cdr.file_name_length = file.path.size();
cdr.local_file_header_offset = file.local_file_header_offset;
if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) {
return HandleError(kIoError);
}
if (fwrite(file.path.data(), 1, file.path.size(), file_) != file.path.size()) {
return HandleError(kIoError);
}
current_offset_ += sizeof(cdr) + file.path.size();
}
EocdRecord er = {};
er.eocd_signature = EocdRecord::kSignature;
er.disk_num = 1;
er.cd_start_disk = 1;
er.num_records_on_disk = files_.size();
er.num_records = files_.size();
er.cd_size = current_offset_ - startOfCdr;
er.cd_start_offset = startOfCdr;
if (fwrite(&er, sizeof(er), 1, file_) != 1) {
return HandleError(kIoError);
}
if (fflush(file_) != 0) {
return HandleError(kIoError);
}
current_offset_ += sizeof(er);
state_ = State::kDone;
return kNoError;
}

View File

@ -0,0 +1,140 @@
/*
* 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.
*/
#include "ziparchive/zip_archive.h"
#include "ziparchive/zip_writer.h"
#include <base/test_utils.h>
#include <gtest/gtest.h>
#include <memory>
struct zipwriter : public ::testing::Test {
TemporaryFile* temp_file_;
int fd_;
FILE* file_;
void SetUp() override {
temp_file_ = new TemporaryFile();
fd_ = temp_file_->fd;
file_ = fdopen(fd_, "w");
ASSERT_NE(file_, nullptr);
}
void TearDown() override {
fclose(file_);
delete temp_file_;
}
};
TEST_F(zipwriter, WriteUncompressedZipWithOneFile) {
ZipWriter writer(file_);
const char* expected = "hello";
ASSERT_EQ(writer.StartEntry("file.txt", 0), 0);
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
ASSERT_EQ(writer.WriteBytes("llo", 3), 0);
ASSERT_EQ(writer.FinishEntry(), 0);
ASSERT_EQ(writer.Finish(), 0);
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
ZipArchiveHandle handle;
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
ZipEntry data;
ASSERT_EQ(FindEntry(handle, ZipString("file.txt"), &data), 0);
EXPECT_EQ(data.compressed_length, strlen(expected));
EXPECT_EQ(data.uncompressed_length, strlen(expected));
EXPECT_EQ(data.method, kCompressStored);
char buffer[6];
EXPECT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(&buffer), sizeof(buffer)),
0);
buffer[5] = 0;
EXPECT_STREQ(expected, buffer);
CloseArchive(handle);
}
TEST_F(zipwriter, WriteUncompressedZipWithMultipleFiles) {
ZipWriter writer(file_);
ASSERT_EQ(writer.StartEntry("file.txt", 0), 0);
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
ASSERT_EQ(writer.FinishEntry(), 0);
ASSERT_EQ(writer.StartEntry("file/file.txt", 0), 0);
ASSERT_EQ(writer.WriteBytes("llo", 3), 0);
ASSERT_EQ(writer.FinishEntry(), 0);
ASSERT_EQ(writer.StartEntry("file/file2.txt", 0), 0);
ASSERT_EQ(writer.FinishEntry(), 0);
ASSERT_EQ(writer.Finish(), 0);
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
ZipArchiveHandle handle;
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
char buffer[4];
ZipEntry data;
ASSERT_EQ(FindEntry(handle, ZipString("file.txt"), &data), 0);
EXPECT_EQ(data.method, kCompressStored);
EXPECT_EQ(data.compressed_length, 2u);
EXPECT_EQ(data.uncompressed_length, 2u);
ASSERT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)),
0);
buffer[2] = 0;
EXPECT_STREQ("he", buffer);
ASSERT_EQ(FindEntry(handle, ZipString("file/file.txt"), &data), 0);
EXPECT_EQ(data.method, kCompressStored);
EXPECT_EQ(data.compressed_length, 3u);
EXPECT_EQ(data.uncompressed_length, 3u);
ASSERT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)),
0);
buffer[3] = 0;
EXPECT_STREQ("llo", buffer);
ASSERT_EQ(FindEntry(handle, ZipString("file/file2.txt"), &data), 0);
EXPECT_EQ(data.method, kCompressStored);
EXPECT_EQ(data.compressed_length, 0u);
EXPECT_EQ(data.uncompressed_length, 0u);
CloseArchive(handle);
}
TEST_F(zipwriter, WriteUncompressedZipWithAlignedFile) {
ZipWriter writer(file_);
ASSERT_EQ(writer.StartEntry("align.txt", ZipWriter::kAlign32), 0);
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
ASSERT_EQ(writer.FinishEntry(), 0);
ASSERT_EQ(writer.Finish(), 0);
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
ZipArchiveHandle handle;
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
ZipEntry data;
ASSERT_EQ(FindEntry(handle, ZipString("align.txt"), &data), 0);
EXPECT_EQ(data.offset & 0x03, 0);
}