Search code examples
c++libzip

Binary files get corrupted while unzipping with libzip


I was required to unzip .zip files in my Qt project. So I installed libzip. I wrote a function to unzip a .zip file given its location and destination directory path. The function is correctly able to unzip plain text files (like .json, .txt, and others) but it always corrupts any type of binary files like PNGs or MP4s. Here is the function I wrote:

void Converter::unzipFile(QString srcFilePath_, QString destinationDirectoryPath_) {
    std::string srcFilePath = srcFilePath_.toStdString();
    std::string destinationDirectoryPath = destinationDirectoryPath_.toStdString();

    char bufferStr[100];
    int error = 0;
    int fileHandle;

    struct zip *zipArchive = zip_open(srcFilePath.c_str(), 0, &error);
    struct zip_file *zippedFile;
    struct zip_stat zippedFileStats;

    if (not QDir().exists(destinationDirectoryPath_)) {
        QDir().mkpath(destinationDirectoryPath_);
    }

    if (zipArchive == NULL) {
        zip_error_to_str(bufferStr, sizeof(bufferStr), error, errno);
        std::cout << "[ERROR] Can not open the zip file: " << error <<  ":\n" << bufferStr;
    }

    for (int index = 0; index < zip_get_num_entries(zipArchive, 0); index++) {
        if (zip_stat_index(zipArchive, index, 0, &zippedFileStats) == 0) {
            int zipFileNameLength = strlen(zippedFileStats.name);

            if (zippedFileStats.name[zipFileNameLength - 1] == '/') {  // i.e. folder
                QDir().mkpath(destinationDirectoryPath_ + "/" + zippedFileStats.name);
            } else {  // i.e. file
                zippedFile = zip_fopen_index(zipArchive, index, 0);
                if (zippedFile == NULL) {
                    qDebug() << "[ERROR] Can not open the file in zip archive.";
                    continue;
                }

                fileHandle = open((destinationDirectoryPath + "/" + zippedFileStats.name).c_str(), O_RDWR | O_TRUNC | O_CREAT, 0644);
                if (fileHandle < 0) {
                    qDebug() << "[ERROR] Can not create the file (into which zipped data is to be extracted).";
                    continue;
                }

                int totalFileDataLength = 0;
                while (totalFileDataLength != (long long) zippedFileStats.size) {
                    int fileDataLength = zip_fread(zippedFile, bufferStr, 100);

                    if (fileDataLength < 0) {
                        qDebug() << "[ERROR] Can not read the zipped file.";
                        exit(1);
                    }

                    write(fileHandle, bufferStr, fileDataLength);
                    totalFileDataLength += fileDataLength;
                }

                close(fileHandle);
                zip_fclose(zippedFile);
            }
        } else {
            qDebug() << "IDK what is here 🫥.";
        }
    }

    if (zip_close(zipArchive) == -1) {
        qDebug() << "[ERROR] Cannot close the zip file.";
    }
}

NOTE: Everything related to Qt works, I have checked all the variables and made sure that they have the right path values. Plain text files get unzipped easily. This problem is only with binary files.

NOTE: I have tried all relevant solutions on the internet but nothing worked so this question is not a duplicate.

What do I do? Any help will be appreiciated.


Solution

  • Instead of this:

                int totalFileDataLength = 0;
                while (totalFileDataLength != (long long) zippedFileStats.size) {
                    int fileDataLength = zip_fread(zippedFile, bufferStr, 100);
    

    This:

                zip_uint64_t totalFileDataLength = 0;
                while (totalFileDataLength != zippedFileStats.size) {
                    zip_int64_t fileDataLength = zip_fread(zippedFile, bufferStr, 100);
    

    I'm not sure if that is your bug, but without the above change, you'll have an issue for any file larger than 2GB. That could result in an infinite loop or truncated file. Since write expects a size_t parameter (which can be 32-bit or 64-bit), you can safely cast if the compiler complains:

    write(fileHandle, bufferStr, (size_t)fileDataLength);
    

    Update

    You need to bail out of the loop when zip_fread returns zero. Let's update the entire loop:

                while (true) {
                    zip_int64_t fileDataLength = zip_fread(zippedFile, bufferStr, 100);
                    if (fileDataLength == 0) {
                       break; // end of file
                    }
    
                    if (fileDataLength < 0) {
                        qDebug() << "[ERROR] Can not read the zipped file.";
                        exit(1);
                    }
    
                    write(fileHandle, bufferStr, (size_t)fileDataLength);
                }
    

    Final Update

    Windows needs you to pass O_BINARY or (_O_BINARY) into the open call.

                fileHandle = open((destinationDirectoryPath + "/" + zippedFileStats.name).c_str(), O_RDWR | O_TRUNC | O_CREAT | O_BINARY, 0644);