Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 47f55819 authored by Adam Lesinski's avatar Adam Lesinski Committed by Gerrit Code Review
Browse files

Merge "ZipWriter: Do not write DataDescriptor for STORED files"

parents e7fbd6a2 e2fa70bc
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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);
@@ -172,6 +173,7 @@ private:
  };

  FILE* file_;
  bool seekable_;
  off64_t current_offset_;
  State state_;
  std::vector<FileEntry> files_;
+91 −47
Original line number Diff line number Diff line
@@ -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?

@@ -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_)),
@@ -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_);
@@ -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) {
@@ -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, &current_file_entry_.last_mod_time, &current_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;
@@ -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 = {};
@@ -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;
}
@@ -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;
@@ -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);
    }
@@ -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);
  }

+1 −0
Original line number Diff line number Diff line
@@ -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));