Подтвердить что ты не робот

Как найти начало "Центрального каталога" в zip файлах?

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

В моем случае я не хотел использовать какие-либо функции сжатия, предлагаемые в любом из 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
     }
 }

Ответ 4

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

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

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

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

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