stachenov / quazip

Qt/C++ wrapper over minizip
Other
580 stars 233 forks source link

The encrypted zip file generated with QuaZip on Ubuntu cannot be decrypted on Windows. #201

Open xiqixi opened 2 months ago

xiqixi commented 2 months ago

quazip-0.7.1 + qt-5.12.12 quazip-1.5 + qt-5.12.12

I generated a password-protected zip file (password is "a") using Qt + QuaZip on Ubuntu. After copying the file to Windows, I tried to extract it using Qt + QuaZip (with the same password "a"), but the extraction failed. Moreover, I couldn't extract the file using unzip on Ubuntu, nor using 7-Zip on Windows. The only way to successfully extract it is by using Qt + QuaZip on Ubuntu. Below is the code I used. How can I make it extract successfully on Windows? Looking forward to your answer. Thank you!

bool ZipHandler::compressFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password)
{
    if (!zip)
    {
        return false;
    }
    if (zip->getMode()!=QuaZip::mdCreate &&
            zip->getMode()!=QuaZip::mdAppend &&
            zip->getMode()!=QuaZip::mdAdd)
    {
        return false;
    }

    QFile inFile;
    inFile.setFileName(fileName);
    if (!inFile.open(QIODevice::ReadOnly))
    {
        return false;
    }

    QuaZipFile outFile(zip);
    if (!password.isEmpty())
    {
        if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName()), password.toUtf8().constData()))
        {
            return false;
        }
    } else
    {
        if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
        {
            return false;
        }
    }

    if (!copyData(inFile, outFile) || outFile.getZipError()!=UNZ_OK)
    {
        return false;
    }

    outFile.close();
    if (outFile.getZipError()!=UNZ_OK)
    {
        return false;
    }
    inFile.close();

    return true;
}

ZipError ZipHandler::extractFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password)
{
    QuaZipFile inFile(zip);
    QuaZipFileInfo64 info;
    QFile::Permissions srcPerm;
    ZipError ret = NoError;

    if (!fileName.isEmpty())
    {
        zip->setCurrentFile(fileName);
    }

    if (!zip->getCurrentFileInfo(&info))
    {
        ret = OtherError;
    }

    if (ret == NoError)
    {
        if (info.isEncrypted())
        {
            if (!inFile.open(QIODevice::ReadOnly, password.toUtf8().constData()) || inFile.getZipError()!=UNZ_OK)
            {
                ret = OtherError;
            }
        } else
        {
            if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError()!=UNZ_OK)
            {
                ret = OtherError;
            }
        }
    }

    if (ret == NoError)
    {
        ret = makepath(fileDest);
    }

    if (ret == NoError)
    {
        srcPerm = info.getPermissions();
        if (fileDest.endsWith(FILEPATH_SEP) && QFileInfo(fileDest).isDir()) {
            if (srcPerm != 0)
            {
                QFile(fileDest).setPermissions(srcPerm);
            }
        } else
        {
            ret = extractData(srcPerm, inFile, fileDest);
        }
    }

    return ret;
}

ZipError ZipHandler::makepath(const QString& fileDest)
{
    QDir curDir;
    ZipError ret = NoError;

    if (fileDest.endsWith(FILEPATH_SEP))
    {
        if (!curDir.mkpath(fileDest))
        {
            ret = NoPermission;
        }
    } else
    {
        if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
        {
            ret = NoPermission;
        }
    }

    return ret;
}

ZipError ZipHandler::extractData(const QFile::Permissions& srcPerm, QuaZipFile& inFile, const QString& fileDest)
{
    QFile outFile;
    ZipError ret = NoError;

    outFile.setFileName(fileDest);
    if (!outFile.open(QIODevice::WriteOnly))
    {
        ret = NoPermission;
    }

    if (ret == NoError)
    {
        if (!copyData(inFile, outFile) || inFile.getZipError()!=UNZ_OK)
        {
            outFile.close();
            removeFile(QStringList(fileDest));
            ret = NoSpace;
        }
    }

    if (ret == NoError)
    {
        outFile.close();
        inFile.close();

        if (inFile.getZipError() != UNZ_OK)
        {
            removeFile(QStringList(fileDest));
            ret = OtherError;
        }
    }

    if (ret == NoError)
    {
        if (srcPerm != 0) {
            outFile.setPermissions(srcPerm);
        }
    }

    return ret;
}

bool ZipHandler::copyData(QIODevice &inFile, QIODevice &outFile)
{
    char buf[writeBuff];
    qint64 readLen = 0;
    bool ret = true;
    qint64 writeBuffNum = 4096;

    while (!inFile.atEnd())
    {
        readLen = inFile.read(buf, writeBuffNum);
        if (readLen <= 0)
        {
            ret = false;
            break;
        }
        if (outFile.write(buf, readLen) != readLen)
        {
            ret = false;
            break;
        }
    }
    return ret;
}

bool ZipHandler::removeFile(const QStringList& listFile)
{
    bool ret = true;

    for (int i=0; i<listFile.count(); ++i)
    {
        ret = ret && QFile::remove(listFile.at(i));
    }
    return ret;
}
cen1 commented 2 months ago

Your code seems to work fine for me.

QFile oldZip("../../xiqixi.zip");
if (oldZip.exists()) oldZip.remove();

QuaZip zip("../../xiqixi .zip");
zip.open(QuaZip::mdCreate);
compressFile(&zip, "../../file.txt", "file2.txt", "a");
zip.close();
if (zip.getZipError() != ZIP_OK) return 1;

I can decompress this on Linux, MacOS and Windows.

Perhaps you are sending full path as fileDest?

xiqixi commented 2 months ago

I can decompress this on Linux, MacOS and Windows.

Perhaps you are sending full path as fileDest? Thank you for your help. The file name of the source file is set as fileDest.

2 comments: 1.The password-protected zip file, generated by Qt and QuaZip on Windows, can be extracted using Qt and QuaZip on Windows or 7-Zip. 2.If no password is set, the zip file generated by Qt and QuaZip on Ubuntu can be extracted with Qt and QuaZip on Windows or 7-Zip.

cen1 commented 2 months ago

Can you send a code snippet just like mine to see how you call the functions?

xiqixi commented 2 months ago

Can you send a code snippet just like mine to see how you call the functions?

#include <ziphandler.h>

StringList fileList;
fileList.append("../9.txt");
ZipHandler::compressFiles("../9.zip", xxx, "1");

ziphandler.h

#ifndef ZIPHANDLER_H
#define ZIPHANDLER_H

#include <QString>
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>

enum ZipError
{
    NoError,
    NoSpace,
    NoPermission,
    OtherError
};

class ZipHandler
{
public:
    ZipHandler();
    ~ZipHandler();

    static bool compressFiles(const QString& fileCompressed, const QStringList& files, const QString& password = QString());
    static bool compressDir(const QString& fileCompressed, const QString& dir = QString(), bool recursive = true, const QString& password = QString());
    static ZipError extractDir(const QString& fileCompressed, const QString& dir = QString(), const QString& password = QString());

private:

    static bool compressFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password);
    static bool compressSubDir(QuaZip* parentZip, const QString& dir, const QString& parentDir, bool recursive = true, const QString& password = QString());
    static ZipError extractFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password);
    static ZipError extractData(const QFile::Permissions& srcPerm, QuaZipFile& inFile, const QString& fileDest);
    static bool copyData(QIODevice &inFile, QIODevice &outFile);
    static ZipError makepath(const QString& fileDest);
    static bool removeFile(const QStringList& listFile);
    static const int writeBuff;
    static const QString FILEPATH_SEP;
};

#endif // ZIPHANDLER_H

ziphandler.cpp

#include <QDir>
#include <ziphandler.h>

const int ZipHandler::writeBuff = 4096;
const QString ZipHandler::FILEPATH_SEP = "/";

ZipHandler::ZipHandler()
{}

ZipHandler::~ZipHandler()
{}

bool ZipHandler::compressFiles(const QString& fileCompressed, const QStringList& files, const QString& password)
{
    QuaZip zip(fileCompressed);
    QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
    if (!zip.open(QuaZip::mdCreate))
    {
        QFile::remove(fileCompressed);
        return false;
    }

    QFileInfo info;
    foreach (QString file, files)
    {
        info.setFile(file);
        if (!info.exists() || !compressFile(&zip, file, info.fileName(), password)) {
            QFile::remove(fileCompressed);
            return false;
        }
    }

    zip.close();
    if (zip.getZipError() != 0)
    {
        QFile::remove(fileCompressed);
        return false;
    }

    return true;
}

bool ZipHandler::compressDir(const QString& fileCompressed, const QString& dir, bool recursive, const QString& password)
{
    QuaZip zip(fileCompressed);
    QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
    if (!zip.open(QuaZip::mdCreate))
    {
        QFile::remove(fileCompressed);
        return false;
    }

    if (!compressSubDir(&zip, dir, dir, recursive, password))
    {
        QFile::remove(fileCompressed);
        return false;
    }

    zip.close();
    if (zip.getZipError()!=0)
    {
        QFile::remove(fileCompressed);
        return false;
    }

    return true;
}

ZipError ZipHandler::extractDir(const QString& fileCompressed, const QString& dir, const QString& password)
{
    QuaZip zip(fileCompressed);
    ZipError ret = NoError;
    QDir directory(dir);
    QStringList extracted;
    QString name;
    QString absFilePath;

    if (!zip.open(QuaZip::mdUnzip))
    {
        ret = OtherError;
    }

    if (ret == NoError)
    {
        if (!zip.goToFirstFile())
        {
            ret = OtherError;
        }
    }

    if (ret == NoError)
    {
        for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile())
        {
            name = zip.getCurrentFileName();
            absFilePath = directory.absoluteFilePath(name);
            ret = extractFile(&zip, "", absFilePath, password);
            if (ret != NoError)
            {
                removeFile(extracted);
                break;
            }
            extracted.append(absFilePath);
        }
    }

    if (ret == NoError)
    {
        zip.close();
    }

    if (ret == NoError)
    {
        if (zip.getZipError()!=0)
        {
            removeFile(extracted);
            ret = OtherError;
        }
    }

    return ret;
}

bool ZipHandler::compressFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password)
{
    if (!zip)
    {
        return false;
    }
    if (zip->getMode()!=QuaZip::mdCreate &&
            zip->getMode()!=QuaZip::mdAppend &&
            zip->getMode()!=QuaZip::mdAdd)
    {
        return false;
    }

    QFile inFile;
    inFile.setFileName(fileName);
    if (!inFile.open(QIODevice::ReadOnly))
    {
        return false;
    }

    QuaZipFile outFile(zip);
    if (!password.isEmpty())
    {
        if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName()), password.toUtf8().constData()))
        {
            return false;
        }
    } else
    {
        if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
        {
            return false;
        }
    }

    if (!copyData(inFile, outFile) || outFile.getZipError()!=UNZ_OK)
    {
        return false;
    }

    outFile.close();
    if (outFile.getZipError()!=UNZ_OK)
    {
        return false;
    }
    inFile.close();

    return true;
}

bool ZipHandler::compressSubDir(QuaZip* parentZip, const QString& dir, const QString& parentDir, bool recursive, const QString& password)
{
    if (!parentZip)
    {
        return false;
    }
    if (parentZip->getMode()!=QuaZip::mdCreate &&
            parentZip->getMode()!=QuaZip::mdAppend &&
            parentZip->getMode()!=QuaZip::mdAdd)
    {
        return false;
    }

    QDir directory(dir);
    if (!directory.exists())
    {
        return false;
    }

    QDir origDirectory(parentDir);
    if (dir != parentDir)
    {
        QuaZipFile dirZipFile(parentZip);
        if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(origDirectory.relativeFilePath(dir) + "/", dir), 0, 0, 0))
        {
            return false;
        }
        dirZipFile.close();
    }

    if (recursive) {
        QFileInfoList files = directory.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot);
        foreach (QFileInfo file, files)
        {
            if (!compressSubDir(parentZip, file.absoluteFilePath(), parentDir, recursive, password))
            {
                return false;
            }
        }
    }

    QFileInfoList files = directory.entryInfoList(QDir::Files);
    foreach (QFileInfo file, files)
    {
        if (!file.isFile() || file.absoluteFilePath() == parentZip->getZipName())
        {
            continue;
        }

        QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
        if (!compressFile(parentZip, file.absoluteFilePath(), filename, password))
        {
            return false;
        }
    }

    return true;
}

ZipError ZipHandler::extractFile(QuaZip* zip, const QString& fileName, const QString& fileDest, const QString& password)
{
    QuaZipFile inFile(zip);
    QuaZipFileInfo64 info;
    QFile::Permissions srcPerm;
    ZipError ret = NoError;

    if (!fileName.isEmpty())
    {
        zip->setCurrentFile(fileName);
    }

    if (!zip->getCurrentFileInfo(&info))
    {
        ret = OtherError;
    }

    if (ret == NoError)
    {
        if (info.isEncrypted())
        {
            if (!inFile.open(QIODevice::ReadOnly, password.toUtf8().constData()) || inFile.getZipError()!=UNZ_OK)
            {
                ret = OtherError;
            }
        } else
        {
            if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError()!=UNZ_OK)
            {
                ret = OtherError;
            }
        }
    }

    if (ret == NoError)
    {
        ret = makepath(fileDest);
    }

    if (ret == NoError)
    {
        srcPerm = info.getPermissions();
        if (fileDest.endsWith(FILEPATH_SEP) && QFileInfo(fileDest).isDir()) {
            if (srcPerm != 0)
            {
                QFile(fileDest).setPermissions(srcPerm);
            }
        } else
        {
            ret = extractData(srcPerm, inFile, fileDest);
        }
    }

    return ret;
}

ZipError ZipHandler::extractData(const QFile::Permissions& srcPerm, QuaZipFile& inFile, const QString& fileDest)
{
    QFile outFile;
    ZipError ret = NoError;

    outFile.setFileName(fileDest);
    if (!outFile.open(QIODevice::WriteOnly))
    {
        ret = NoPermission;
    }

    if (ret == NoError)
    {
        if (!copyData(inFile, outFile) || inFile.getZipError()!=UNZ_OK)
        {
            outFile.close();
            removeFile(QStringList(fileDest));
            ret = NoSpace;
        }
    }

    if (ret == NoError)
    {
        outFile.close();
        inFile.close();

        if (inFile.getZipError() != UNZ_OK)
        {
            removeFile(QStringList(fileDest));
            ret = OtherError;
        }
    }

    if (ret == NoError)
    {
        if (srcPerm != 0) {
            outFile.setPermissions(srcPerm);
        }
    }

    return ret;
}

bool ZipHandler::copyData(QIODevice &inFile, QIODevice &outFile)
{
    char buf[writeBuff];
    qint64 readLen = 0;
    bool ret = true;
    qint64 writeBuffNum = 4096;

    while (!inFile.atEnd())
    {
        readLen = inFile.read(buf, writeBuffNum);
        if (readLen <= 0)
        {
            ret = false;
            break;
        }
        if (outFile.write(buf, readLen) != readLen)
        {
            ret = false;
            break;
        }
    }
    return ret;
}

ZipError ZipHandler::makepath(const QString& fileDest)
{
    QDir curDir;
    ZipError ret = NoError;

    if (fileDest.endsWith(FILEPATH_SEP))
    {
        if (!curDir.mkpath(fileDest))
        {
            ret = NoPermission;
        }
    } else
    {
        if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
        {
            ret = NoPermission;
        }
    }

    return ret;
}

bool ZipHandler::removeFile(const QStringList& listFile)
{
    bool ret = true;

    for (int i=0; i<listFile.count(); ++i)
    {
        ret = ret && QFile::remove(listFile.at(i));
    }
    return ret;
}