Описание формата ZIP файла. Часть 1

ZIP файл состоит из трех областей:

  • сжатые/несжатые данные, (последовательность структур типа LocalFileHeader, сами данные и необязательных DataDescriptor)
  • центральный каталог (последовательность структур CentralDirectoryFileHeader)
  • описание центрального каталога (End of central directory record (EOCD))

С начала файла идет набор из LocalFileHeader, непосредственно данные и (необязательно) структура Data descriptor. Затем структуры типа CentralDirectoryFileHeader для каждого файла и папки в ZIP архиве и завершает все это структура End of central directory record.

Local File Header

Используется для описания метаданных файла (имя файла, контрольная сумма, время и дата модификации, сжатый/несжатый размер). Как правило сразу после этой структуры следует содержимое файла.

LocalFileHeader

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 (контрольная сумма, сжатый/несжатый размер).

Откровенно говоря, мне такие файлы не попадались, поэтому больше того, чем написано в википедии сказать не могу.

DataDescriptor

struct DataDescriptor
{
    // Необязательная сигнатура, равна 0x08074b50
    uint32_t signature;
    // Контрольная сумма
    uint32_t crc32;
    // Сжатый размер
    uint32_t compressedSize;
    // Несжатый размер
    uint32_t uncompressedSize;
};

Central directory file header

Расширенное описание метаданных файла. Содержит дополненную версию LocalFileHeader (добавляются поля номер диска, файловые атрибуты, смещение до LocalFileHeader от начала ZIP файла).

CenterDirectoryFileHeader

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.

EOCD

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 с нулевым размером и контрольной суммой. Название папки заканчивается слешем «/».

Конструктор, деструктор и автоматический стек в C++

С удивлением узнал об интересном способе использования стека в нестандартных целях. Посмотрите внимательно на код ниже.

Его фишка в том, что класс Sample в конструкторе сохраняет предыдущий инстанс типа Sample, а в деструкторе — восстанавливает. При этом метод Sample::instance() всегда будет возвращать текущий объект типа Sample.

Не имею ни малейшего понятия — зачем это может вам понадобиться, но мне пришлось столкнуться с таким впервые, так что спешу поделиться с общественностью.

class Sample
{
public:
    Sample()
        : prevInst_s(curInst_s)
    {
        curInst_s = this;
    }
 
    ~Sample()
    {
        curInst_s = prevInst_s;
    }
 
    static Sample *instance()
    {
        return curInst_s;
    }
 
private:
    Sample *prevInst_s;
    static Sample *curInst_s;
};
 
Sample *Sample::curInst_s = NULL;

Пример использования такого функционала под катом. (в комментариях указано — какой instance сейчас текущий)

#include <stdio.h>
 
class Sample
{
public:
    Sample(const char *name)
        : name_(name)
        , prevInst_s(curInst_s)
    {
        curInst_s = this;
        printf("current: %s\n", name_);
    }
 
    ~Sample()
    {
        curInst_s = prevInst_s;
 
        if (curInst_s)
        {
            printf("restore: %s\n", curInst_s->name_);
        }
    }
 
    static Sample *instance()
    {
        return curInst_s;
    }
 
private:
    const char *name_;
    Sample *prevInst_s;
    static Sample *curInst_s;
};
 
Sample *Sample::curInst_s = NULL;
 
 
int main()
{
    /*
     *
     * Sample::instance() => NULL
     *
     */
    {
        Sample sampleA("A");
        /*
         *
         * Sample::instance() => A
         *
         */
        {
            Sample sampleB("B");
            /*
             *
             *  Sample::instance() => B
             *
             */
        }
        /* 
         * 
         * Sample::instance() => A
         *
         */
        {
            Sample sampleC("C");
            /*
             *
             * Sample::instance() => C
             *
             */
            {
                Sample sampleD("D");
                /*
                 *
                 * Sample::instance() => D
                 *
                 */
            }
            /*
             *
             * Sample::instance() => C
             *
             */
        }
        /*
         *
         * Sample::instance() => A
         *
         */
    }
    /*
     *
     * Sample::instance() => NULL
     *
     */
}
Блог Евгения Жирнова