Запись 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