summaryrefslogblamecommitdiff
path: root/src/fs/zip.cpp
blob: 57aa9fc76a1b53754b7bd968d5467acd59e046cd (plain) (tree)

















































































                                                                            
                                                  

































                                                                               



                                                         








                                                                   
                                                     








                                                                   
                                                    





















                                                                             
/*
 *  The ManaPlus Client
 *  Copyright (C) 2017  The ManaPlus Developers
 *
 *  This file is part of The ManaPlus Client.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "fs/zip.h"

#include "fs/ziplocalheader.h"

#include "utils/checkutils.h"
#include "utils/stringutils.h"

#include <iostream>
#include <unistd.h>

#include "debug.h"

#define readVal(val, sz, msg) \
    cnt = fread(static_cast<void*>(val), 1, sz, arcFile); \
    if (cnt != sz) \
    { \
        reportAlways("Error reading " msg " in file %s", \
            archiveName.c_str()); \
        delete header; \
        delete [] buf; \
        fclose(arcFile); \
        return false; \
    } \

namespace Zip
{
    bool readArchiveInfo(const std::string &restrict archiveName,
                         std::vector<ZipLocalHeader*> &restrict headers)
    {
        FILE *restrict const arcFile = fopen(archiveName.c_str(),
            "r");
        if (arcFile == nullptr)
        {
            reportAlways("Can't open zip file %s",
                archiveName.c_str());
            return false;
        }
        size_t cnt = 0U;
        uint8_t *const buf = new uint8_t[65535 + 10];
        uint16_t val16 = 0U;
        ZipLocalHeader *header = nullptr;

        logger->log("Read archive: %s", archiveName.c_str());
        // format source https://en.wikipedia.org/wiki/Zip_%28file_format%29
        while (feof(arcFile) == 0)
        {
            // file header pointer on 0
            // read file header signature
            readVal(buf, 4, "zip file header");  // + 4
            // pointer on 4

            if (buf[0] == 0x50 &&
                buf[1] == 0x4B &&
                buf[2] == 0x03 &&
                buf[3] == 0x04)
            {   // local file header
                header = new ZipLocalHeader;
                header->archiveName = archiveName;
                // skip useless fields
                fseek(arcFile, 14, SEEK_CUR);  // + 14
                // file header pointer on 18
                readVal(&header->compressSize, 4,
                    "zip compressed size")  // + 4
                // file header pointer on 22
                // +++ need add endian specific decoding for val32
                readVal(&header->uncompressSize, 4,
                    "zip uncompressed size")  // + 4
                // file header pointer on 26
                // +++ need add endian specific decoding for val32
                readVal(&val16, 2, "file name length")  // + 2
                // file header pointer on 28
                const uint32_t fileNameLen = CAST_U32(val16);
                if (fileNameLen > 1000)
                {
                    reportAlways("Error too long file name in file %s",
                        archiveName.c_str());
                    delete header;
                    delete [] buf;
                    fclose(arcFile);
                    return false;
                }
                readVal(&val16, 2, "extra field length")  // + 2
                // file header pointer on 30
                const uint32_t extraFieldLen = CAST_U32(val16);
                readVal(buf, fileNameLen, "file name");
                // file header pointer on 30 + fileNameLen
                buf[fileNameLen] = 0;
                header->fileName = std::string(
                    reinterpret_cast<char*>(buf));
                header->dataOffset = ftell(arcFile) + extraFieldLen;
                fseek(arcFile, extraFieldLen + header->compressSize, SEEK_CUR);
                // pointer on 30 + fileNameLen + extraFieldLen + compressSize
                if (findLast(header->fileName, "/") == false)
                {
                    headers.push_back(header);
                    logger->log(" file name: %s",
                        header->fileName.c_str());
                    logger->log(" compressed size: %u",
                        header->compressSize);
                    logger->log(" uncompressed size: %u",
                        header->uncompressSize);
                }
            }
            else if (buf[0] == 0x50 &&
                     buf[1] == 0x4B &&
                     buf[2] == 0x01 &&
                     buf[3] == 0x02)
            {   // central directory file header
                // !!! This is quick way for read zip archives. !!!
                // !!! It ignore modified files in archive. !!!
                // ignoring central directory entries
                break;
            }
            else if (buf[0] == 0x50 &&
                     buf[1] == 0x4B &&
                     buf[2] == 0x05 &&
                     buf[3] == 0x06)
            {   // end of central directory
                // !!! This is quick way for read zip archives. !!!
                // !!! It ignore modified files in archive. !!!
                // ignoring end of central directory
                break;
            }
            else
            {
                reportAlways("Error in header signature (0x%02x%02x%02x%02x)"
                    " in file %s",
                    buf[0],
                    buf[1],
                    buf[2],
                    buf[3],
                    archiveName.c_str());
                delete [] buf;
                fclose(arcFile);
                return false;
            }
        }
        delete [] buf;
        fclose(arcFile);
        return true;
    }

}  // namespace Zip