Pregunta ¿Cómo se puede encontrar el inicio del "Directorio central" en archivos zip?


Wikipedia tiene una excelente descripción del formato de archivo ZIP, pero la estructura del "directorio central" me resulta confusa. Específicamente esto:

Este orden permite que se cree un archivo ZIP en una sola pasada, pero generalmente se descomprime leyendo primero el directorio central al final.

El problema es que incluso el encabezado final del directorio central es de longitud variable. ¿Cómo, entonces, alguien puede obtener el inicio del directorio central para analizar?

(Ah, y pasé un tiempo mirando APPNOTE.TXT en vano antes de venir aquí y preguntar: P)


20
2018-01-26 07:02


origen


Respuestas:


Mis condolencias, al leer la descripción de la wikipedia, me da la muy fuerte impresión de que debe hacer una buena cantidad de trabajo de adivinar + verificar:

Busque hacia atrás desde el final para la etiqueta de fin de directorio 0x06054b50, busque 16 bytes para encontrar la compensación para la etiqueta de inicio de directorio 0x02014b50, y espero que así sea. Podría hacer algunas comprobaciones de cordura, como buscar la longitud de comentario y las etiquetas de cadena de comentario después de la etiqueta de fin de directorio, pero parece que los decodificadores Zip funcionan porque las personas no ponen caracteres divertidos en sus comentarios zip, nombres de archivo, etc. adelante. Basado enteramente en la página de wikipedia, de todos modos.


11
2018-01-26 07:14



Implementé la compatibilidad de archivos zip hace algún tiempo y busco los últimos kilobytes para un final de la firma de directorio central (4 bytes). Eso funciona bastante bien, hasta que alguien ponga 50kb de texto en el comentario (lo que es poco probable que suceda. Para estar absolutamente seguro, puede buscar los últimos 64kb + unos pocos bytes, ya que el tamaño del comentario es de 16 bits). Después de eso, busco el extremo zip64 del localizador central de directorios, eso es más fácil ya que tiene una estructura fija.


7
2018-01-26 08:56



Aquí hay una solución que acabo de implementar en caso de que alguien lo necesite. Esto implica agarrar el directorio central.

En mi caso, no quería ninguna de las funciones de compresión que se ofrecen en ninguna de las soluciones zip. Solo quería saber sobre los contenidos. El siguiente código devolverá un ZipArchive de una lista de cada entrada en el zip.

También utiliza una cantidad mínima de acceso a archivos y asignación de memoria.

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

Uso:

 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
     }
 }

2
2017-12-15 13:22



En caso de que alguien por ahí todavía esté luchando con este problema, eche un vistazo al repositorio que alojé en GitHub que contiene mi proyecto y que podría responder a sus preguntas.

Lector de archivos zip Básicamente lo que hace es descargar el central directory parte de .zip archivo que reside en el final del archivo. Luego leerá todos los nombres de archivos y carpetas con su ruta desde los bytes e imprimirá en la consola.

Hice comentarios sobre los pasos más complicados en mi código fuente.

El programa solo puede funcionar hasta unos archivos .zip de 4 GB. Después de eso, tendrá que hacer algunos cambios en el tamaño de la VM y tal vez más.

Disfruta :)


0
2018-01-06 19:03