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».