Запись 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);
} |
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(); |
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)); |
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