ZIP файл состоит из трех областей:
- сжатые/несжатые данные, (последовательность структур типа
LocalFileHeader
, сами данные и необязательныхDataDescriptor
) - центральный каталог (последовательность структур
CentralDirectoryFileHeader
) - описание центрального каталога (
End of central directory record (EOCD)
)
С начала файла идет набор из LocalFileHeader
, непосредственно данные и (необязательно) структура Data descriptor
. Затем структуры типа CentralDirectoryFileHeader
для каждого файла и папки в ZIP архиве и завершает все это структура End of central directory record
.
Local File Header
Используется для описания метаданных файла (имя файла, контрольная сумма, время и дата модификации, сжатый/несжатый размер). Как правило сразу после этой структуры следует содержимое файла.
struct LocalFileHeader
{
// Обязательная сигнатура, равна 0x04034b50
uint32_t signature;
// Минимальная версия для распаковки
uint16_t versionToExtract;
// Битовый флаг
uint16_t generalPurposeBitFlag;
// Метод сжатия (0 - без сжатия, 8 - deflate)
uint16_t compressionMethod;
// Время модификации файла
uint16_t modificationTime;
// Дата модификации файла
uint16_t modificationDate;
// Контрольная сумма
uint32_t crc32;
// Сжатый размер
uint32_t compressedSize;
// Несжатый размер
uint32_t uncompressedSize;
// Длина название файла
uint16_t filenameLength;
// Длина поля с дополнительными данными
uint16_t extraFieldLength;
// Название файла (размером filenameLength)
uint8_t *filename;
// Дополнительные данные (размером extraFieldLength)
uint8_t *extraField;
};
compressedSize
при использовании сжатия или размером uncompressedSize
в противном случае.
Иногда бывает невозможно вычислить данные на момент записи LocalFileHeader
, тогда в crc32
, compressedSize
и uncompressedSize
записываются нули, третий бит в generalPurposeBitFlag
ставится в единицу, а после LocalFileHeader
добавляется структура типа DataDescriptor
.
Data descriptor
Если по какой-то причине содержимое файла невозможно создать одновременно с заголовком типа LocalFileHeader
, то сразу после него следует структура DataDescriptor
, где идет находится дополнение метаданных для LocalFileHeader
(контрольная сумма, сжатый/несжатый размер).
Откровенно говоря, мне такие файлы не попадались, поэтому больше того, чем написано в википедии сказать не могу.
struct DataDescriptor
{
// Необязательная сигнатура, равна 0x08074b50
uint32_t signature;
// Контрольная сумма
uint32_t crc32;
// Сжатый размер
uint32_t compressedSize;
// Несжатый размер
uint32_t uncompressedSize;
};
Central directory file header
Расширенное описание метаданных файла. Содержит дополненную версию LocalFileHeader
(добавляются поля номер диска, файловые атрибуты, смещение до LocalFileHeader
от начала ZIP файла).
struct CentralDirectoryFileHeader
{
// Обязательная сигнатура, равна 0x02014b50
uint32_t signature;
// Версия для создания
uint16_t versionMadeBy;
// Минимальная версия для распаковки
uint16_t versionToExtract;
// Битовый флаг
uint16_t generalPurposeBitFlag;
// Метод сжатия (0 - без сжатия, 8 - deflate)
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;
// Смещение до структуры LocalFileHeader
uint32_t localFileHeaderOffset;
// Имя файла (длиной filenameLength)
uint8_t *filename;
// Дополнительные данные (длиной extraFieldLength)
uint8_t *extraField;
// Комментарий к файла (длиной fileCommentLength)
uint8_t *fileComment;
};
End of central directory record (EOCD)
Эта структура записывается в конце файла. Содержит следующие поля: номер текущего диска, количество записей CentralDirectoryFileHeader
в текущем диске, общее количество записей CentralDirectoryFileHeader
.
struct EOCD
{
// Обязательная сигнатура, равна 0x06054b50
uint32_t signature;
// Номер диска
uint16_t diskNumber;
// Номер диска, где находится начало Central Directory
uint16_t startDiskNumber;
// Количество записей в Central Directory в текущем диске
uint16_t numberCentralDirectoryRecord;
// Всего записей в Central Directory
uint16_t totalCentralDirectoryRecord;
// Размер Central Directory
uint32_t sizeOfCentralDirectory;
// Смещение Central Directory
uint32_t centralDirectoryOffset;
// Длина комментария
uint16_t commentLength;
// Комментарий (длиной commentLength)
uint8_t *comment;
};
Папки в ZIP файле представлены двумя структурами LocalFileHeader
и CentralDirectoryFileHeader
с нулевым размером и контрольной суммой. Название папки заканчивается слешем «/».
Спасибо, очень полезно!
Всегда пожалуйста!
Кстати, если архивируется ОДИН файл размером <4Гб, то наличие Central directory необязательно.
Достаточно Local File Header либо Local File Header+Data descriptor.
Проверено.
…проверено.
Но работает такой вариант только в Far Manager 3 !!!
К сожалению, Winrar и 7zip на такие файлы сильно ругаются.
Та же история и с архивами из одиночного файла, размер которого >4Gb.
Если использовать ТОЛЬКО Local File Header + ZIP64ExtendInformation в ExtraFieldRecord,
то Far3 так же прекрасно открывает такой файл, а винрар и 7зип пасуют…
Так что для всеобщей совместимости придётся всё-таки добавлять в архив Central directory :(
Я писал в статье про чтение ZIP файла что можно читать только Local File Header без EOCD. Просто FarManager умеет читать сломанные архивы без секции EOCD. Собственно, восстановление ZIP файла не читает EOCD, а сразу бегает по Local File Header.
«Если по какой-то причине содержимое файла невозможно создать одновременно с заголовком типа LocalFileHeader, то сразу после него следует структура DataDescriptor, где идет находится дополнение метаданных для LocalFileHeader (контрольная сумма, сжатый/несжатый размер).»
Тут что-то немного напутано.
Если архиватор не имеет возможности сразу заполнить все поля LocalFileHeader [не известны размер исходного файла и/или размер данных после сжатия и/или чексумма исходного файла],
то он просто забивает соответствующие поля в заголовке LocalFileHeader нулями и взводит в том же заголовке флаг [бит 3, т.е. получается 0x0008].
И уже после того как все данные пожаты и стали известны и CRC, и длины файла до и после сжатия, к концу сжатых данных добавляется DataDescriptor, из которого потом при разархивировании и берутся нужные сведения.
Проверял: в Far3 эта схема работает :)
Ну не понятно же :)
— А если серьезно, ну вот есть заголовок «Local File Header», который начинается с 0 байта самого ZIP-файла, по его размеру можно определить его окончание. НО, что начинается после окончания заголовка «Local File Header» ??
Сами сжатые данные ? Но тогда, где и каким образом располагается заголовок/заголовки «Central directory file header» ? И как из расположение учитывать при считывании самих сжатых данных ?
-Так же не понятно, где начинается заголовок «End of central directory record».
В общем к сожалению статья больше написана просто для галочки, и если честно почти ничего не проясняет.
«End of central directory record» находится в самом конце файла. Его точное расположение: размер_архива — размер_структуры_EOCD. В этой же структуре содержится смещение для чтения первого «Central directory file header» в поле «centralDirectoryOffset».