You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt项目中使用libzip解压时二进制文件损坏的问题求助

解决libzip解压二进制文件损坏问题

问题描述

在Qt项目中使用libzip编写的unzipFile函数可正常解压纯文本文件(如.json、.txt),但解压PNG、MP4等二进制文件时会出现损坏。代码如下:

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.";
    }
}

已知Qt功能正常,路径变量正确,仅二进制文件解压损坏。

问题根源

  1. 变量类型不匹配导致溢出:totalFileDataLength是int类型,而zippedFileStats.size是zip_uint64_t(64位无符号整数)。当二进制文件大小超过int的最大值(通常为2GB)时,totalFileDataLength会溢出,导致循环提前终止,文件写入不完整。
  2. 未检查write返回值:write函数可能无法一次性写入所有请求的字节,直接忽略返回值会导致部分数据丢失。
  3. 跨平台二进制模式缺失:在Windows系统中,open函数默认以文本模式打开文件,会将二进制数据中的换行符(0x0A)转换为系统换行符(0x0D0A),破坏二进制文件结构。

修复后的代码

#include <fcntl.h>
#ifdef _WIN32
#include <io.h>
#define O_BINARY _O_BINARY
#endif

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

    char buffer[1024]; // 增大缓冲区提升解压效率
    int error = 0;
    int fileHandle;

    struct zip *zipArchive = zip_open(srcFilePath.c_str(), 0, &error);
    if (!zipArchive) {
        char errorStr[100];
        zip_error_to_str(errorStr, sizeof(errorStr), error, errno);
        qDebug() << "[ERROR] Failed to open zip file:" << error << "\n" << errorStr;
        return; // 避免后续空指针访问
    }

    struct zip_file *zippedFile;
    struct zip_stat zippedFileStats;

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

    int entryCount = zip_get_num_entries(zipArchive, 0);
    for (int index = 0; index < entryCount; index++) {
        if (zip_stat_index(zipArchive, index, 0, &zippedFileStats) != 0) {
            qDebug() << "[ERROR] Failed to stat entry at index" << index;
            continue;
        }

        const char* entryName = zippedFileStats.name;
        size_t nameLength = strlen(entryName);
        if (nameLength > 0 && entryName[nameLength - 1] == '/') {
            // 创建目录
            QString dirPath = destinationDirectoryPath_ + "/" + entryName;
            if (!QDir().mkpath(dirPath)) {
                qDebug() << "[ERROR] Failed to create directory:" << dirPath;
            }
            continue;
        }

        // 打开压缩包内文件
        zippedFile = zip_fopen_index(zipArchive, index, 0);
        if (!zippedFile) {
            qDebug() << "[ERROR] Failed to open entry:" << entryName;
            continue;
        }

        // 创建目标文件,添加二进制模式
        QString destFilePath = destinationDirectoryPath_ + "/" + entryName;
        fileHandle = open(destFilePath.toStdString().c_str(), O_RDWR | O_TRUNC | O_CREAT | O_BINARY, 0644);
        if (fileHandle < 0) {
            qDebug() << "[ERROR] Failed to create file:" << destFilePath;
            zip_fclose(zippedFile);
            continue;
        }

        zip_uint64_t totalWritten = 0;
        while (totalWritten < zippedFileStats.size) {
            zip_int64_t bytesRead = zip_fread(zippedFile, buffer, sizeof(buffer));
            if (bytesRead < 0) {
                qDebug() << "[ERROR] Failed to read entry:" << entryName;
                close(fileHandle);
                zip_fclose(zippedFile);
                return;
            } else if (bytesRead == 0) {
                break; // 提前结束,避免死循环
            }

            // 确保所有字节都写入
            zip_int64_t bytesWritten = write(fileHandle, buffer, bytesRead);
            if (bytesWritten < 0) {
                qDebug() << "[ERROR] Failed to write to file:" << destFilePath;
                close(fileHandle);
                zip_fclose(zippedFile);
                return;
            }
            totalWritten += bytesWritten;
        }

        // 验证写入长度是否匹配
        if (totalWritten != zippedFileStats.size) {
            qDebug() << "[WARNING] Incomplete write for file:" << destFilePath 
                     << "Expected:" << zippedFileStats.size << "Wrote:" << totalWritten;
        }

        close(fileHandle);
        zip_fclose(zippedFile);
    }

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

关键修复点说明

  • 变量类型统一:将totalWritten改为zip_uint64_t,与zippedFileStats.size类型一致,避免溢出问题。
  • 检查write返回值:循环写入直到所有字节都被写入目标文件,确保数据完整性。
  • 添加二进制模式:在open参数中加入O_BINARY,Windows下强制以二进制模式打开文件,避免换行符转换破坏二进制数据。
  • 增大缓冲区:将缓冲区从100字节改为1024字节,提升解压效率。
  • 完善错误处理:在zip_open失败后直接返回,避免后续空指针操作;添加写入长度验证,及时发现不完整写入问题。

内容的提问来源于stack exchange,提问作者Manbir Judge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 00:05:28