diff --git a/include/ziparchive/zip_writer.h b/include/ziparchive/zip_writer.h index 0efade80a..0b6ede45c 100644 --- a/include/ziparchive/zip_writer.h +++ b/include/ziparchive/zip_writer.h @@ -86,11 +86,27 @@ public: */ int32_t StartEntry(const char* path, size_t flags); + /** + * Starts a new zip entry with the given path and flags, where the + * entry will be aligned to the given alignment. + * Flags can only be ZipWriter::kCompress. Using the flag ZipWriter::kAlign32 + * will result in an error. + * 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 StartAlignedEntry(const char* path, size_t flags, uint32_t alignment); + /** * 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); + /** + * Same as StartAlignedEntry(const char*, size_t), but sets a last modified time for the entry. + */ + int32_t StartAlignedEntryWithTime(const char* path, size_t flags, time_t time, + uint32_t alignment); + /** * Writes bytes to the zip file for the previously started zip entry. * Returns 0 on success, and an error value < 0 on failure. diff --git a/libziparchive/zip_writer.cc b/libziparchive/zip_writer.cc index f117cc5af..1ebed30b1 100644 --- a/libziparchive/zip_writer.cc +++ b/libziparchive/zip_writer.cc @@ -20,12 +20,19 @@ #include +#include + #include #include #include +#include #include #define DEF_MEM_LEVEL 8 // normally in zutil.h? +#if !defined(powerof2) +#define powerof2(x) ((((x)-1)&(x))==0) +#endif + /* Zip compression methods we support */ enum { kCompressStored = 0, // no compression @@ -50,6 +57,12 @@ static const int32_t kInvalidEntryName = -3; // An error occurred in zlib. static const int32_t kZlibError = -4; +// The start aligned function was called with the aligned flag. +static const int32_t kInvalidAlign32Flag = -5; + +// The alignment parameter is not a power of 2. +static const int32_t kInvalidAlignment = -6; + static const char* sErrorCodes[] = { "Invalid state", "IO error", @@ -102,7 +115,25 @@ int32_t ZipWriter::HandleError(int32_t error_code) { } int32_t ZipWriter::StartEntry(const char* path, size_t flags) { - return StartEntryWithTime(path, flags, time_t()); + uint32_t alignment = 0; + if (flags & kAlign32) { + flags &= ~kAlign32; + alignment = 4; + } + return StartAlignedEntryWithTime(path, flags, time_t(), alignment); +} + +int32_t ZipWriter::StartAlignedEntry(const char* path, size_t flags, uint32_t alignment) { + return StartAlignedEntryWithTime(path, flags, time_t(), alignment); +} + +int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) { + uint32_t alignment = 0; + if (flags & kAlign32) { + flags &= ~kAlign32; + alignment = 4; + } + return StartAlignedEntryWithTime(path, flags, time, alignment); } static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) { @@ -126,11 +157,20 @@ 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; } -int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) { +int32_t ZipWriter::StartAlignedEntryWithTime(const char* path, size_t flags, + time_t time, uint32_t alignment) { if (state_ != State::kWritingZip) { return kInvalidState; } + if (flags & kAlign32) { + return kInvalidAlign32Flag; + } + + if (powerof2(alignment) == 0) { + return kInvalidAlignment; + } + FileInfo fileInfo = {}; fileInfo.path = std::string(path); fileInfo.local_file_header_offset = current_offset_; @@ -166,11 +206,14 @@ int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t tim header.file_name_length = fileInfo.path.size(); off64_t offset = current_offset_ + sizeof(header) + fileInfo.path.size(); - if ((flags & ZipWriter::kAlign32) && (offset & 0x03)) { + std::vector zero_padding; + if (alignment != 0 && (offset & (alignment - 1))) { // Pad the extra field so the data will be aligned. - uint16_t padding = 4 - (offset % 4); + uint16_t padding = alignment - (offset % alignment); header.extra_field_length = padding; offset += padding; + zero_padding.resize(padding); + memset(zero_padding.data(), 0, zero_padding.size()); } if (fwrite(&header, sizeof(header), 1, file_) != 1) { @@ -181,7 +224,9 @@ int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t tim return HandleError(kIoError); } - if (fwrite("\0\0\0", 1, header.extra_field_length, file_) != header.extra_field_length) { + if (header.extra_field_length != 0 && + fwrite(zero_padding.data(), 1, header.extra_field_length, file_) + != header.extra_field_length) { return HandleError(kIoError); } diff --git a/libziparchive/zip_writer_test.cc b/libziparchive/zip_writer_test.cc index b7d145833..fe0846db6 100644 --- a/libziparchive/zip_writer_test.cc +++ b/libziparchive/zip_writer_test.cc @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -122,7 +123,7 @@ TEST_F(zipwriter, WriteUncompressedZipWithMultipleFiles) { CloseArchive(handle); } -TEST_F(zipwriter, WriteUncompressedZipWithAlignedFile) { +TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlag) { ZipWriter writer(file_); ASSERT_EQ(0, writer.StartEntry("align.txt", ZipWriter::kAlign32)); @@ -142,6 +143,103 @@ TEST_F(zipwriter, WriteUncompressedZipWithAlignedFile) { CloseArchive(handle); } +void ConvertZipTimeToTm(uint32_t& zip_time, struct tm* tm) { + memset(tm, 0, sizeof(struct tm)); + tm->tm_hour = (zip_time >> 11) & 0x1f; + tm->tm_min = (zip_time >> 5) & 0x3f; + tm->tm_sec = (zip_time & 0x1f) << 1; + + tm->tm_year = ((zip_time >> 25) & 0x7f) + 80; + tm->tm_mon = ((zip_time >> 21) & 0xf) - 1; + tm->tm_mday = (zip_time >> 16) & 0x1f; +} + +TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedFlagAndTime) { + ZipWriter writer(file_); + + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr); + time_t time = mktime(&tm); + ASSERT_EQ(0, writer.StartEntryWithTime("align.txt", ZipWriter::kAlign32, time)); + ASSERT_EQ(0, writer.WriteBytes("he", 2)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + + ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); + + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); + + ZipEntry data; + ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data)); + EXPECT_EQ(0, data.offset & 0x03); + + struct tm mod; + ConvertZipTimeToTm(data.mod_time, &mod); + EXPECT_EQ(tm.tm_sec, mod.tm_sec); + EXPECT_EQ(tm.tm_min, mod.tm_min); + EXPECT_EQ(tm.tm_hour, mod.tm_hour); + EXPECT_EQ(tm.tm_mday, mod.tm_mday); + EXPECT_EQ(tm.tm_mon, mod.tm_mon); + EXPECT_EQ(tm.tm_year, mod.tm_year); + + CloseArchive(handle); +} + +TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValue) { + ZipWriter writer(file_); + + ASSERT_EQ(0, writer.StartAlignedEntry("align.txt", 0, 4096)); + ASSERT_EQ(0, writer.WriteBytes("he", 2)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + + ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); + + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); + + ZipEntry data; + ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data)); + EXPECT_EQ(0, data.offset & 0xfff); + + CloseArchive(handle); +} + +TEST_F(zipwriter, WriteUncompressedZipFileWithAlignedValueAndTime) { + ZipWriter writer(file_); + + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + ASSERT_TRUE(strptime("18:30:20 1/12/2001", "%H:%M:%S %d/%m/%Y", &tm) != nullptr); + time_t time = mktime(&tm); + ASSERT_EQ(0, writer.StartAlignedEntryWithTime("align.txt", 0, time, 4096)); + ASSERT_EQ(0, writer.WriteBytes("he", 2)); + ASSERT_EQ(0, writer.FinishEntry()); + ASSERT_EQ(0, writer.Finish()); + + ASSERT_GE(0, lseek(fd_, 0, SEEK_SET)); + + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFd(fd_, "temp", &handle, false)); + + ZipEntry data; + ASSERT_EQ(0, FindEntry(handle, ZipString("align.txt"), &data)); + EXPECT_EQ(0, data.offset & 0xfff); + + struct tm mod; + ConvertZipTimeToTm(data.mod_time, &mod); + EXPECT_EQ(tm.tm_sec, mod.tm_sec); + EXPECT_EQ(tm.tm_min, mod.tm_min); + EXPECT_EQ(tm.tm_hour, mod.tm_hour); + EXPECT_EQ(tm.tm_mday, mod.tm_mday); + EXPECT_EQ(tm.tm_mon, mod.tm_mon); + EXPECT_EQ(tm.tm_year, mod.tm_year); + + CloseArchive(handle); +} + TEST_F(zipwriter, WriteCompressedZipWithOneFile) { ZipWriter writer(file_); @@ -206,3 +304,10 @@ TEST_F(zipwriter, WriteCompressedZipFlushFull) { CloseArchive(handle); } + +TEST_F(zipwriter, CheckStartEntryErrors) { + ZipWriter writer(file_); + + ASSERT_EQ(-5, writer.StartAlignedEntry("align.txt", ZipWriter::kAlign32, 4096)); + ASSERT_EQ(-6, writer.StartAlignedEntry("align.txt", 0, 3)); +}