Как найти начало «Центрального каталога» в zip-файлах? - PullRequest
25 голосов
/ 26 января 2011

В Википедии есть отличное описание формата файла ZIP , но структура "центрального каталога" меня смущает.В частности это:

Этот порядок позволяет создать файл ZIP за один проход, но обычно он распаковывается при первом чтении центрального каталога в конце.

Проблема в том, что даже конечный заголовок для центрального каталога имеет переменную длину.Как тогда кто-то может получить начало центрального каталога для анализа?

(О, и я потратил некоторое время на тщательное рассмотрение APPNOTE.TXT, прежде чем прийти сюда и спросить: P)

Ответы [ 4 ]

15 голосов
/ 26 января 2011

Мои соболезнования, чтение описания в Википедии дает мне очень сильное впечатление, что вам нужно сделать немало догадок + проверить работу:

Выполняйте поиск в обратном направлении от конца для тега конца каталога 0x06054b50, ищите 16 байтов, чтобы найти смещение для тега начала каталога 0x02014b50, и надеемся, что это так. Вы могли бы сделать некоторые проверки работоспособности, такие как поиск длины комментария и тегов строки комментария после тега конца каталога, но кажется, что Zip-декодеры работают, потому что люди не помещают забавные символы в свои zip-комментарии, имена файлов и т.д. вперед. Во всяком случае, полностью на странице википедии.

7 голосов
/ 26 января 2011

Некоторое время назад я реализовывал поддержку zip-архивов и ищу последние несколько килобайт конца сигнатуры центрального каталога (4 байта).Это работает довольно хорошо, пока кто-нибудь не поместит 50-килобайтный текст в комментарий (что вряд ли произойдет. Чтобы быть абсолютно уверенным, вы можете искать последние 64 КБ + несколько байтов, так как размер комментария составляет 16 бит).После этого я ищу конец zip64 центрального локатора каталогов, это проще, поскольку он имеет фиксированную структуру.

4 голосов
/ 15 декабря 2015

Вот решение, которое мне только что пришлось развернуть, если это кому-нибудь понадобится. Это включает захват центрального каталога.

В моем случае я не хотел использовать какие-либо функции сжатия, которые предлагаются в любом из zip-решений. Я просто хотел узнать о содержании. Следующий код вернет ZipArchive списка каждой записи в zip-архиве.

Он также использует минимальный объем доступа к файлам и выделения памяти.

TinyZip.cpp

#include "TinyZip.h"
#include <cstdio>

namespace TinyZip
{
#define VALID_ZIP_SIGNATURE 0x04034b50
#define CENTRAL_DIRECTORY_EOCD 0x06054b50 //signature
#define CENTRAL_DIRECTORY_ENTRY_SIGNATURE 0x02014b50
#define PTR_OFFS(type, mem, offs) *((type*)(mem + offs)) //SHOULD BE OK 

    typedef struct {
        unsigned int signature : 32;
        unsigned int number_of_disk : 16;
        unsigned int disk_where_cd_starts : 16;
        unsigned int number_of_cd_records : 16;
        unsigned int total_number_of_cd_records : 16;
        unsigned int size_of_cd : 32;
        unsigned int offset_of_start : 32;
        unsigned int comment_length : 16;
    } ZipEOCD;

    ZipArchive* ZipArchive::GetArchive(const char *filepath)
    {
        FILE *pFile = nullptr;
#ifdef WIN32
        errno_t err;
        if ((err = fopen_s(&pFile, filepath, "rb")) == 0)
#else
        if ((pFile = fopen(filepath, "rb")) == NULL)
#endif
        {
            int fileSignature = 0;
            //Seek to start and read zip header
            fread(&fileSignature, sizeof(int), 1, pFile);
            if (fileSignature != VALID_ZIP_SIGNATURE) return false;

            //Grab the file size
            long fileSize = 0;
            long currPos = 0;

            fseek(pFile, 0L, SEEK_END);
            fileSize = ftell(pFile);
            fseek(pFile, 0L, SEEK_SET);

            //Step back the size of the ZipEOCD 
            //If it doesn't have any comments, should get an instant signature match
            currPos = fileSize;
            int signature = 0;
            while (currPos > 0)
            {
                fseek(pFile, currPos, SEEK_SET);
                fread(&signature, sizeof(int), 1, pFile);
                if (signature == CENTRAL_DIRECTORY_EOCD)
                {
                    break;
                }
                currPos -= sizeof(char); //step back one byte
            }

            if (currPos != 0)
            {
                ZipEOCD zipOECD;
                fseek(pFile, currPos, SEEK_SET);
                fread(&zipOECD, sizeof(ZipEOCD), 1, pFile);

                long memBlockSize = fileSize - zipOECD.offset_of_start;

                //Allocate zip archive of size
                ZipArchive *pArchive = new ZipArchive(memBlockSize);

                //Read in the whole central directory (also includes the ZipEOCD...)
                fseek(pFile, zipOECD.offset_of_start, SEEK_SET);
                fread((void*)pArchive->m_MemBlock, memBlockSize - 10, 1, pFile);
                long currMemBlockPos = 0;
                long currNullTerminatorPos = -1;
                while (currMemBlockPos < memBlockSize)
                {
                    int sig = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos);
                    if (sig != CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
                    {
                        if (sig == CENTRAL_DIRECTORY_EOCD) return pArchive;
                        return nullptr; //something went wrong
                    }

                    if (currNullTerminatorPos > 0)
                    {
                        pArchive->m_MemBlock[currNullTerminatorPos] = '\0';
                        currNullTerminatorPos = -1;
                    }

                    const long offsToFilenameLen = 28;
                    const long offsToFieldLen = 30;
                    const long offsetToFilename = 46;

                    int filenameLength = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos + offsToFilenameLen);
                    int extraFieldLen = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos + offsToFieldLen);
                    const char *pFilepath = &pArchive->m_MemBlock[currMemBlockPos + offsetToFilename];
                    currNullTerminatorPos = (currMemBlockPos + offsetToFilename) + filenameLength;
                    pArchive->m_Entries.push_back(pFilepath);

                    currMemBlockPos += (offsetToFilename + filenameLength + extraFieldLen);
                }

                return pArchive;
            }
        }
        return nullptr;
    }

    ZipArchive::ZipArchive(long size)
    {
        m_MemBlock = new char[size];
    }

    ZipArchive::~ZipArchive()
    {
        delete[] m_MemBlock;
    }

    const std::vector<const char*>  &ZipArchive::GetEntries()
    {
        return m_Entries;
    }
}

TinyZip.h

#ifndef __TinyZip__
#define __TinyZip__

#include <vector>
#include <string>

namespace TinyZip
{
    class ZipArchive
    {
    public:
        ZipArchive(long memBlockSize);
        ~ZipArchive();

        static ZipArchive* GetArchive(const char *filepath);

        const std::vector<const char*>  &GetEntries();

    private:
        std::vector<const char*> m_Entries;
        char *m_MemBlock;
    };

}


#endif

Использование:

 TinyZip::ZipArchive *pArchive = TinyZip::ZipArchive::GetArchive("Scripts_unencrypt.pak");
 if (pArchive != nullptr)
 {
     const std::vector<const char*> entries = pArchive->GetEntries();
     for (auto entry : entries)
     {
         //do stuff
     }
 }
0 голосов
/ 06 января 2015

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

Средство чтения Zip-файлов В основномон загружает часть central directory файла .zip, которая находится в конце файла.Затем он будет считывать каждое имя файла и папки с указанием пути из байтов и выводить его на консоль.

Я прокомментировал более сложные шаги в моем исходном коде.

Программа может работать только до 4 ГБ .zip файлов.После этого вам придется внести некоторые изменения в размер виртуальной машины и, возможно, больше.

Наслаждайтесь:)

...