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功能正常,路径变量正确,仅二进制文件解压损坏。
问题根源
- 变量类型不匹配导致溢出:
totalFileDataLength是int类型,而zippedFileStats.size是zip_uint64_t(64位无符号整数)。当二进制文件大小超过int的最大值(通常为2GB)时,totalFileDataLength会溢出,导致循环提前终止,文件写入不完整。 - 未检查
write返回值:write函数可能无法一次性写入所有请求的字节,直接忽略返回值会导致部分数据丢失。 - 跨平台二进制模式缺失:在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
相关产品推荐
相关产品推荐

