Запись ZIP файла

Запись ZIP файла практически операция чтения наоборот:

  • для каждого файла в архиве необходимо записать LocalFileHeader, затем содержимое файла, предварительно посчитав контрольную сумму
  • для каждой записи LocalFileHeader, записываем CentralDirectoryFileHeader
  • записываем EOCD

Теперь подробнее по пунктам

Запись Local File Header

Для каждого файла в архиве существуют две записи — это LocalFileHeader и CentralDirectoryFileHeader. При записи необходимо создавать эти структуры одновременно, только LocalFileHeader записывать сразу, а для CentralDirectoryFileHeader сохранять информацию во временный список для использования ее потом. Также для данных необходимо посчитать контрольную сумму с помощью функции crc32(), которую предоставляет библиотека zlib.

Пример кода

struct LocalFileHeader
{
    uint16_t versionToExtract;
    uint16_t generalPurposeBitFlag;
    uint16_t compressionMethod;
    uint16_t modificationTime;
    uint16_t modificationDate;
    uint32_t crc32;
    uint32_t compressedSize;
    uint32_t uncompressedSize;
    uint16_t filenameLength;
    uint16_t extraFieldLength;
 
} __attribute__((packed));
 
struct FileInfo
{
    uint32_t compressedSize;
    uint32_t uncompressedSize;
    uint16_t compressionMethod;
    uint32_t crc32;
    uint32_t offset;
};
 
// Буфер для чтения
std::vector<uint8_t> readBuffer;
// Буфер для сжатых данных
std::vector<uint8_t> dataBuffer;
// Информация для создания Central directory file header
std::vector<FileInfo> fileInfoList;
// Список файлов
std::vector<std::string> filenames;
 
for (size_t i = 0; i < filenames.size(); ++i)
{
    const std::string filename = filenames[i];
 
    // Открываем файл для чтения
    std::ifstream f(filename.c_str(), std::ios::binary | std::ios::in);
 
    // Структура типа Local File Header
    LocalFileHeader lfh;
    memset(&lfh, 0, sizeof(lfh));
 
    // Узнаем размер файла
    f.seekg(0, f.end);
    lfh.uncompressedSize = f.tellg();
    f.seekg(0, f.beg);
 
    // Считаем весь файл целиком в readBuffer
    readBuffer.resize(lfh.uncompressedSize);
    f.read((char *) readBuffer.data(), lfh.uncompressedSize);
 
    // Считаем контрольную сумму
    lfh.crc32 = crc32(0, readBuffer.data(), lfh.uncompressedSize);
 
    // Выделим память для сжатых данных
    dataBuffer.resize(lfh.uncompressedSize);
 
    // Структура для сжатия данных
    z_stream zStream;
    memset(&zStream, 0, sizeof(zStream));
    deflateInit2(
        &zStream, 
        Z_BEST_SPEED,
        Z_DEFLATED,
        -MAX_WBITS,
        8,
        Z_DEFAULT_STRATEGY);
 
    // Сжимаем данные
    zStream.avail_in = lfh.uncompressedSize;
    zStream.next_in = readBuffer.data();
    zStream.avail_out = lfh.uncompressedSize;
    zStream.next_out = dataBuffer.data();
    deflate(&zStream, Z_FINISH);
 
    // Размер сжатых данных
    lfh.compressedSize = zStream.total_out;
    lfh.compressionMethod = Z_DEFLATED;
 
    // Очистка
    deflateEnd(&zStream);
 
    // Длина имени файла
    lfh.filenameLength = filename.size();
 
    // Сохраним смещение к записи Local File Header внутри архива
    const uint32_t lfhOffset = os.tellp();
 
    // Запишем сигнатуру Local File Header
    const uint32_t signature = 0x04034b50;
    os.write((char *) &signature, sizeof(signature));
 
    // Запишем Local File Header
    os.write((char *) &lfh, sizeof(lfh));
    // Запишем имя файла
    os.write(filename.c_str(), filename.size());
    // Запишем данные
    os.write((char *) dataBuffer.data(), lfh.compressedSize);
 
    // Сохраним все данные для Central directory file header
    FileInfo fileInfo;
    fileInfo.compressedSize = lfh.compressedSize;
    fileInfo.uncompressedSize = lfh.uncompressedSize;
    fileInfo.compressionMethod = lfh.compressionMethod;
    fileInfo.crc32 = lfh.crc32;
    fileInfo.offset = lfhOffset;
    fileInfoList.push_back(fileInfo);
}

Запись Central directory file header

Используя данные, полученные на предыдущем шаге, записываем для каждого файла структуру типа CentralDirectoryFileHeader. Предварительно необходимо сохранить смещение в файле для финальной записи EOCD.

Пример кода

struct CentralDirectoryFileHeader
{
    uint16_t versionMadeBy;
    uint16_t versionToExtract;
    uint16_t generalPurposeBitFlag;
    uint16_t compressionMethod;
    uint16_t modificationTime;
    uint16_t modificationDate;
    uint32_t crc32;
    uint32_t compressedSize;
    uint32_t uncompressedSize;
    uint16_t filenameLength;
    uint16_t extraFieldLength;
    uint16_t fileCommentLength;
    uint16_t diskNumber;
    uint16_t internalFileAttributes;
    uint32_t externalFileAttributes;
    uint32_t localFileHeaderOffset;
 
} __attribute__((packed));
 
// Смещение первой записи для EOCD    
const uint32_t firstOffsetCDFH = os.tellp();
 
for (int i = 0; i < filenames.size(); ++i)
{
    const std::string &filename = filenames[i];
    const FileInfo &fileInfo = fileInfoList[i];
    CentralDirectoryFileHeader cdfh;
    memset(&cdfh, 0, sizeof(cdfh));
 
    cdfh.compressedSize = fileInfo.compressedSize;
    cdfh.uncompressedSize = fileInfo.uncompressedSize;
    cdfh.compressionMethod = fileInfo.compressionMethod;
    cdfh.crc32 = fileInfo.crc32;
    cdfh.localFileHeaderOffset = fileInfo.offset;
    cdfh.filenameLength = filename.size();
 
    // Запишем сигнатуру
    const uint32_t signature = 0x02014b50;
    os.write((char *) &signature, sizeof(signature));
 
    // Запишем структуру
    os.write((char *) &cdfh, sizeof(cdfh));
 
    // Имя файла
    os.write(filename.c_str(), cdfh.filenameLength);
}
 
// Посчитаем размер данных для следующего шага
const uint32_t lastOffsetCDFH = os.tellp();

Запись EOCD

Самый простой этап — сформировать и записать структуру типа EOCD.

Пример кода

EOCD eocd;
memset(&eocd, 0, sizeof(eocd));
eocd.centralDirectoryOffset = firstOffsetCDFH;
eocd.numberCentralDirectoryRecord = filenames.size();
eocd.totalCentralDirectoryRecord = filenames.size();
eocd.sizeOfCentralDirectory = lastOffsetCDFH - firstOffsetCDFH;
 
// Пишем сигнатуру
const uint32_t signature = 0x06054b50;
os.write((char *) &signature, sizeof(signature));
 
// Пишем EOCD
os.write((char *) &eocd, sizeof(eocd));

Замечания по коду:

  • ничего не раскрыто по поводу структуру DataDescriptor (ни разу не сталкивался)
  • иногда размер сжатых данных превышает размер несжатых, поэтому выделяйте память по науке (смотри документацию zlib)
  • если размер данных очень большой, то придется воспользоваться структурой DataDescriptor
Комментариев нет. Будьте первым!
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Блог Евгения Жирнова