Loading include/ziparchive/zip_writer.h +3 −1 Original line number Diff line number Diff line Loading @@ -75,7 +75,8 @@ public: uint32_t uncompressed_size; uint16_t last_mod_time; uint16_t last_mod_date; uint32_t local_file_header_offset; uint32_t padding_length; off64_t local_file_header_offset; }; static const char* ErrorCodeString(int32_t error_code); Loading Loading @@ -172,6 +173,7 @@ private: }; FILE* file_; bool seekable_; off64_t current_offset_; State state_; std::vector<FileEntry> files_; Loading libziparchive/zip_writer.cc +91 −47 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <cstdio> #include <sys/param.h> #include <sys/stat.h> #include <zlib.h> #define DEF_MEM_LEVEL 8 // normally in zutil.h? Loading Loading @@ -84,11 +85,19 @@ static void DeleteZStream(z_stream* stream) { delete stream; } ZipWriter::ZipWriter(FILE* f) : file_(f), current_offset_(0), state_(State::kWritingZip), z_stream_(nullptr, DeleteZStream), buffer_(kBufSize) { ZipWriter::ZipWriter(FILE* f) : file_(f), seekable_(false), current_offset_(0), state_(State::kWritingZip), z_stream_(nullptr, DeleteZStream), buffer_(kBufSize) { // Check if the file is seekable (regular file). If fstat fails, that's fine, subsequent calls // will fail as well. struct stat file_stats; if (fstat(fileno(f), &file_stats) == 0) { seekable_ = S_ISREG(file_stats.st_mode); } } ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_), seekable_(writer.seekable_), current_offset_(writer.current_offset_), state_(writer.state_), files_(std::move(writer.files_)), Loading @@ -100,6 +109,7 @@ ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_), ZipWriter& ZipWriter::operator=(ZipWriter&& writer) { file_ = writer.file_; seekable_ = writer.seekable_; current_offset_ = writer.current_offset_; state_ = writer.state_; files_ = std::move(writer.files_); Loading Loading @@ -159,6 +169,30 @@ static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_da *out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; } static void CopyFromFileEntry(const ZipWriter::FileEntry& src, bool use_data_descriptor, LocalFileHeader* dst) { dst->lfh_signature = LocalFileHeader::kSignature; if (use_data_descriptor) { // Set this flag to denote that a DataDescriptor struct will appear after the data, // containing the crc and size fields. dst->gpb_flags |= kGPBDDFlagMask; // The size and crc fields must be 0. dst->compressed_size = 0u; dst->uncompressed_size = 0u; dst->crc32 = 0u; } else { dst->compressed_size = src.compressed_size; dst->uncompressed_size = src.uncompressed_size; dst->crc32 = src.crc32; } dst->compression_method = src.compression_method; dst->last_mod_time = src.last_mod_time; dst->last_mod_date = src.last_mod_date; dst->file_name_length = src.path.size(); dst->extra_field_length = src.padding_length; } int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags, time_t time, uint32_t alignment) { if (state_ != State::kWritingZip) { Loading @@ -173,66 +207,58 @@ int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags, return kInvalidAlignment; } current_file_entry_ = {}; current_file_entry_.path = path; current_file_entry_.local_file_header_offset = current_offset_; FileEntry file_entry = {}; file_entry.local_file_header_offset = current_offset_; file_entry.path = path; if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(current_file_entry_.path.data()), current_file_entry_.path.size())) { if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(file_entry.path.data()), file_entry.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; if (flags & ZipWriter::kCompress) { current_file_entry_.compression_method = kCompressDeflated; file_entry.compression_method = kCompressDeflated; int32_t result = PrepareDeflate(); if (result != kNoError) { return result; } } else { current_file_entry_.compression_method = kCompressStored; file_entry.compression_method = kCompressStored; } header.compression_method = current_file_entry_.compression_method; ExtractTimeAndDate(time, ¤t_file_entry_.last_mod_time, ¤t_file_entry_.last_mod_date); header.last_mod_time = current_file_entry_.last_mod_time; header.last_mod_date = current_file_entry_.last_mod_date; header.file_name_length = current_file_entry_.path.size(); ExtractTimeAndDate(time, &file_entry.last_mod_time, &file_entry.last_mod_date); off64_t offset = current_offset_ + sizeof(header) + current_file_entry_.path.size(); off_t offset = current_offset_ + sizeof(LocalFileHeader) + file_entry.path.size(); std::vector<char> zero_padding; if (alignment != 0 && (offset & (alignment - 1))) { // Pad the extra field so the data will be aligned. uint16_t padding = alignment - (offset % alignment); header.extra_field_length = padding; file_entry.padding_length = padding; offset += padding; zero_padding.resize(padding); memset(zero_padding.data(), 0, zero_padding.size()); zero_padding.resize(padding, 0); } LocalFileHeader header = {}; // Always start expecting a data descriptor. When the data has finished being written, // if it is possible to seek back, the GPB flag will reset and the sizes written. CopyFromFileEntry(file_entry, true /*use_data_descriptor*/, &header); if (fwrite(&header, sizeof(header), 1, file_) != 1) { return HandleError(kIoError); } if (fwrite(path, sizeof(*path), current_file_entry_.path.size(), file_) != current_file_entry_.path.size()) { if (fwrite(path, sizeof(*path), file_entry.path.size(), file_) != file_entry.path.size()) { return HandleError(kIoError); } if (header.extra_field_length != 0 && fwrite(zero_padding.data(), 1, header.extra_field_length, file_) != header.extra_field_length) { if (file_entry.padding_length != 0 && fwrite(zero_padding.data(), 1, file_entry.padding_length, file_) != file_entry.padding_length) { return HandleError(kIoError); } current_file_entry_ = std::move(file_entry); current_offset_ = offset; state_ = State::kWritingEntry; return kNoError; Loading Loading @@ -405,10 +431,12 @@ int32_t ZipWriter::FinishEntry() { } } if ((current_file_entry_.compression_method & kCompressDeflated) || !seekable_) { // Some versions of ZIP don't allow STORED data to have a trailing DataDescriptor. // If this file is not seekable, or if the data is compressed, write a DataDescriptor. const uint32_t sig = DataDescriptor::kOptSignature; if (fwrite(&sig, sizeof(sig), 1, file_) != 1) { state_ = State::kError; return kIoError; return HandleError(kIoError); } DataDescriptor dd = {}; Loading @@ -418,10 +446,26 @@ int32_t ZipWriter::FinishEntry() { if (fwrite(&dd, sizeof(dd), 1, file_) != 1) { return HandleError(kIoError); } current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd); } else { // Seek back to the header and rewrite to include the size. if (fseeko(file_, current_file_entry_.local_file_header_offset, SEEK_SET) != 0) { return HandleError(kIoError); } files_.emplace_back(std::move(current_file_entry_)); LocalFileHeader header = {}; CopyFromFileEntry(current_file_entry_, false /*use_data_descriptor*/, &header); current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd); if (fwrite(&header, sizeof(header), 1, file_) != 1) { return HandleError(kIoError); } if (fseeko(file_, current_offset_, SEEK_SET) != 0) { return HandleError(kIoError); } } files_.emplace_back(std::move(current_file_entry_)); state_ = State::kWritingZip; return kNoError; } Loading @@ -431,7 +475,7 @@ int32_t ZipWriter::Finish() { return kInvalidState; } off64_t startOfCdr = current_offset_; off_t startOfCdr = current_offset_; for (FileEntry& file : files_) { CentralDirectoryRecord cdr = {}; cdr.record_signature = CentralDirectoryRecord::kSignature; Loading @@ -443,7 +487,7 @@ int32_t ZipWriter::Finish() { 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; cdr.local_file_header_offset = static_cast<uint32_t>(file.local_file_header_offset); if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) { return HandleError(kIoError); } Loading Loading @@ -473,7 +517,7 @@ int32_t ZipWriter::Finish() { // Since we can BackUp() and potentially finish writing at an offset less than one we had // already written at, we must truncate the file. if (ftruncate64(fileno(file_), current_offset_) != 0) { if (ftruncate(fileno(file_), current_offset_) != 0) { return HandleError(kIoError); } Loading libziparchive/zip_writer_test.cc +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ TEST_F(zipwriter, WriteUncompressedZipWithOneFile) { ZipEntry data; ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data)); EXPECT_EQ(kCompressStored, data.method); EXPECT_EQ(0u, data.has_data_descriptor); EXPECT_EQ(strlen(expected), data.compressed_length); ASSERT_EQ(strlen(expected), data.uncompressed_length); ASSERT_TRUE(AssertFileEntryContentsEq(expected, handle, &data)); Loading Loading
include/ziparchive/zip_writer.h +3 −1 Original line number Diff line number Diff line Loading @@ -75,7 +75,8 @@ public: uint32_t uncompressed_size; uint16_t last_mod_time; uint16_t last_mod_date; uint32_t local_file_header_offset; uint32_t padding_length; off64_t local_file_header_offset; }; static const char* ErrorCodeString(int32_t error_code); Loading Loading @@ -172,6 +173,7 @@ private: }; FILE* file_; bool seekable_; off64_t current_offset_; State state_; std::vector<FileEntry> files_; Loading
libziparchive/zip_writer.cc +91 −47 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <cstdio> #include <sys/param.h> #include <sys/stat.h> #include <zlib.h> #define DEF_MEM_LEVEL 8 // normally in zutil.h? Loading Loading @@ -84,11 +85,19 @@ static void DeleteZStream(z_stream* stream) { delete stream; } ZipWriter::ZipWriter(FILE* f) : file_(f), current_offset_(0), state_(State::kWritingZip), z_stream_(nullptr, DeleteZStream), buffer_(kBufSize) { ZipWriter::ZipWriter(FILE* f) : file_(f), seekable_(false), current_offset_(0), state_(State::kWritingZip), z_stream_(nullptr, DeleteZStream), buffer_(kBufSize) { // Check if the file is seekable (regular file). If fstat fails, that's fine, subsequent calls // will fail as well. struct stat file_stats; if (fstat(fileno(f), &file_stats) == 0) { seekable_ = S_ISREG(file_stats.st_mode); } } ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_), seekable_(writer.seekable_), current_offset_(writer.current_offset_), state_(writer.state_), files_(std::move(writer.files_)), Loading @@ -100,6 +109,7 @@ ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_), ZipWriter& ZipWriter::operator=(ZipWriter&& writer) { file_ = writer.file_; seekable_ = writer.seekable_; current_offset_ = writer.current_offset_; state_ = writer.state_; files_ = std::move(writer.files_); Loading Loading @@ -159,6 +169,30 @@ static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_da *out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; } static void CopyFromFileEntry(const ZipWriter::FileEntry& src, bool use_data_descriptor, LocalFileHeader* dst) { dst->lfh_signature = LocalFileHeader::kSignature; if (use_data_descriptor) { // Set this flag to denote that a DataDescriptor struct will appear after the data, // containing the crc and size fields. dst->gpb_flags |= kGPBDDFlagMask; // The size and crc fields must be 0. dst->compressed_size = 0u; dst->uncompressed_size = 0u; dst->crc32 = 0u; } else { dst->compressed_size = src.compressed_size; dst->uncompressed_size = src.uncompressed_size; dst->crc32 = src.crc32; } dst->compression_method = src.compression_method; dst->last_mod_time = src.last_mod_time; dst->last_mod_date = src.last_mod_date; dst->file_name_length = src.path.size(); dst->extra_field_length = src.padding_length; } int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags, time_t time, uint32_t alignment) { if (state_ != State::kWritingZip) { Loading @@ -173,66 +207,58 @@ int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags, return kInvalidAlignment; } current_file_entry_ = {}; current_file_entry_.path = path; current_file_entry_.local_file_header_offset = current_offset_; FileEntry file_entry = {}; file_entry.local_file_header_offset = current_offset_; file_entry.path = path; if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(current_file_entry_.path.data()), current_file_entry_.path.size())) { if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(file_entry.path.data()), file_entry.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; if (flags & ZipWriter::kCompress) { current_file_entry_.compression_method = kCompressDeflated; file_entry.compression_method = kCompressDeflated; int32_t result = PrepareDeflate(); if (result != kNoError) { return result; } } else { current_file_entry_.compression_method = kCompressStored; file_entry.compression_method = kCompressStored; } header.compression_method = current_file_entry_.compression_method; ExtractTimeAndDate(time, ¤t_file_entry_.last_mod_time, ¤t_file_entry_.last_mod_date); header.last_mod_time = current_file_entry_.last_mod_time; header.last_mod_date = current_file_entry_.last_mod_date; header.file_name_length = current_file_entry_.path.size(); ExtractTimeAndDate(time, &file_entry.last_mod_time, &file_entry.last_mod_date); off64_t offset = current_offset_ + sizeof(header) + current_file_entry_.path.size(); off_t offset = current_offset_ + sizeof(LocalFileHeader) + file_entry.path.size(); std::vector<char> zero_padding; if (alignment != 0 && (offset & (alignment - 1))) { // Pad the extra field so the data will be aligned. uint16_t padding = alignment - (offset % alignment); header.extra_field_length = padding; file_entry.padding_length = padding; offset += padding; zero_padding.resize(padding); memset(zero_padding.data(), 0, zero_padding.size()); zero_padding.resize(padding, 0); } LocalFileHeader header = {}; // Always start expecting a data descriptor. When the data has finished being written, // if it is possible to seek back, the GPB flag will reset and the sizes written. CopyFromFileEntry(file_entry, true /*use_data_descriptor*/, &header); if (fwrite(&header, sizeof(header), 1, file_) != 1) { return HandleError(kIoError); } if (fwrite(path, sizeof(*path), current_file_entry_.path.size(), file_) != current_file_entry_.path.size()) { if (fwrite(path, sizeof(*path), file_entry.path.size(), file_) != file_entry.path.size()) { return HandleError(kIoError); } if (header.extra_field_length != 0 && fwrite(zero_padding.data(), 1, header.extra_field_length, file_) != header.extra_field_length) { if (file_entry.padding_length != 0 && fwrite(zero_padding.data(), 1, file_entry.padding_length, file_) != file_entry.padding_length) { return HandleError(kIoError); } current_file_entry_ = std::move(file_entry); current_offset_ = offset; state_ = State::kWritingEntry; return kNoError; Loading Loading @@ -405,10 +431,12 @@ int32_t ZipWriter::FinishEntry() { } } if ((current_file_entry_.compression_method & kCompressDeflated) || !seekable_) { // Some versions of ZIP don't allow STORED data to have a trailing DataDescriptor. // If this file is not seekable, or if the data is compressed, write a DataDescriptor. const uint32_t sig = DataDescriptor::kOptSignature; if (fwrite(&sig, sizeof(sig), 1, file_) != 1) { state_ = State::kError; return kIoError; return HandleError(kIoError); } DataDescriptor dd = {}; Loading @@ -418,10 +446,26 @@ int32_t ZipWriter::FinishEntry() { if (fwrite(&dd, sizeof(dd), 1, file_) != 1) { return HandleError(kIoError); } current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd); } else { // Seek back to the header and rewrite to include the size. if (fseeko(file_, current_file_entry_.local_file_header_offset, SEEK_SET) != 0) { return HandleError(kIoError); } files_.emplace_back(std::move(current_file_entry_)); LocalFileHeader header = {}; CopyFromFileEntry(current_file_entry_, false /*use_data_descriptor*/, &header); current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd); if (fwrite(&header, sizeof(header), 1, file_) != 1) { return HandleError(kIoError); } if (fseeko(file_, current_offset_, SEEK_SET) != 0) { return HandleError(kIoError); } } files_.emplace_back(std::move(current_file_entry_)); state_ = State::kWritingZip; return kNoError; } Loading @@ -431,7 +475,7 @@ int32_t ZipWriter::Finish() { return kInvalidState; } off64_t startOfCdr = current_offset_; off_t startOfCdr = current_offset_; for (FileEntry& file : files_) { CentralDirectoryRecord cdr = {}; cdr.record_signature = CentralDirectoryRecord::kSignature; Loading @@ -443,7 +487,7 @@ int32_t ZipWriter::Finish() { 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; cdr.local_file_header_offset = static_cast<uint32_t>(file.local_file_header_offset); if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) { return HandleError(kIoError); } Loading Loading @@ -473,7 +517,7 @@ int32_t ZipWriter::Finish() { // Since we can BackUp() and potentially finish writing at an offset less than one we had // already written at, we must truncate the file. if (ftruncate64(fileno(file_), current_offset_) != 0) { if (ftruncate(fileno(file_), current_offset_) != 0) { return HandleError(kIoError); } Loading
libziparchive/zip_writer_test.cc +1 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ TEST_F(zipwriter, WriteUncompressedZipWithOneFile) { ZipEntry data; ASSERT_EQ(0, FindEntry(handle, ZipString("file.txt"), &data)); EXPECT_EQ(kCompressStored, data.method); EXPECT_EQ(0u, data.has_data_descriptor); EXPECT_EQ(strlen(expected), data.compressed_length); ASSERT_EQ(strlen(expected), data.uncompressed_length); ASSERT_TRUE(AssertFileEntryContentsEq(expected, handle, &data)); Loading