如何使用miniz创建一个可以被gzip解压的压缩文件?

6
我正在Windows平台上用C++编写程序。我想压缩存储在char[]数组中的一些数据,并将其输出到文件中,稍后我将上传该文件到Unix服务器,并希望可以通过gzip -d进行解压缩。

经过大量研究,我选择了miniz。此外,我在这里找到了gzip文件格式。

以下是创建gzip文件的代码片段:(对不起,我没有放一些变量的定义;它们在其他地方定义)

unsigned long zsize;
zpkg[0] = 0x1F;
zpkg[1] = 0x8B;
zpkg[2] = 8;
zpkg[3] = 0;
zpkg[4] = 0;
zpkg[5] = 0;
zpkg[6] = 0;
zpkg[7] = 0;
zpkg[8] = 0;
zpkg[9] = 0xFF;
compress2(zpkg + 10, &zsize, pkg, pkgSize, MZ_DEFAULT_LEVEL);
int footerStart = (int)zsize + 10;
mz_ulong crc = mz_crc32(MZ_CRC32_INIT, zpkg + 10, zsize);
zpkg[footerStart] = crc & 0xFF;
zpkg[footerStart + 1] = (crc >> 8) & 0xFF;
zpkg[footerStart + 2] = (crc >> 16) & 0xFF;
zpkg[footerStart + 3] = (crc >> 24) & 0xFF;
zpkg[footerStart + 4] = pkgSize & 0xFF;
zpkg[footerStart + 5] = (pkgSize >> 8) & 0xFF;
zpkg[footerStart + 6] = (pkgSize >> 16) & 0xFF;
zpkg[footerStart + 7] = (pkgSize >> 24) & 0xFF;

之后只需将zpkg数组输出到文件即可。但是这样做不起作用;当我使用gzip解压缩时,出现了以下错误消息:

gzip: data stream error
gzip: test.gz: uncompress failed

请问有没有人能指出我做错了什么?



感谢Mark Adler和Michael的帮助,我找到了一个可行的解决方案。

首先,像Mark指出的那样,我应该使miniz返回一个原始的deflate数据流。这可以通过将-MZ_DEFAULT_WINDOW_BITS(注意减号)作为第四个参数传递给mz_deflateInit2()来实现。查看miniz源代码,compress2()函数最终调用mz_deflateInit2()并传递MZ_DEFAULT_WINDOW_BITS,这意味着添加zlib头和尾。因此,最简单的修复方法是在那里添加一个减号,这样我仍然可以使用compress2()函数。(这对我有效,因为我只在一个地方调用此函数)

其次,就像Michael指出的那样,CRC码应该在未压缩的数据上计算。所以我像这样修复它:

mz_ulong crc = mz_crc32(MZ_CRC32_INIT, pkg, pkgSize);

在进行了以上两个更改之后,gzip -d不再出现错误提示。

CRC32(CRC-32)包含未压缩数据的循环冗余校验值。看起来你正在对压缩数据进行CRC处理。 - Michael
谢谢Michael,这是我不确定的另一件事情,但我尝试破解有效gzip文件的CRC代码并查看错误消息,结果是“gzip:无效的压缩数据--crc错误”。因此,我相信这不是我的问题的根本原因。 - Wen-Hsin Hsieh
嗯,这可能是需要修复的一件事情。你是如何精确地将数据写入文件的?你是否在十六进制编辑器中打开了生成的文件,并确保它看起来像你预期的那样?你是否将其与使用其他工具压缩的文件进行了比较? - Michael
我使用ofstream将其输出到文件。实际上这只是为了测试,真正的情况是通过WinHTTP将其上传到服务器,但这与我的问题无关,所以我没有提到它。我尝试使用gzip压缩相同的数据,但内容完全不同。由于miniz文档说它与zlib兼容,所以我假设compress2()函数可以给我gzip主体,但看起来并不是这样。所以我想知道我是否正确地使用了miniz库?或者我错了,有一个正确的答案吗?或者我不能用这种方式使用miniz? - Wen-Hsin Hsieh
2个回答

2
compress2() 生成的是一个zlib流,其中包含有zlib头和尾的deflate压缩数据。根据您的需求,您只需要原始的deflate压缩流,以便将其放入手动生成的gzip头文件和尾文件中。
您可以选择:a) 丢弃 compress2() 的输出的前两个字节和后四个字节,以去掉zlib头和尾;b) 使用 deflateInit2()deflate()deflateEnd() 替代 compress2() 并选择原始的deflate格式;或者c) 使用这些相同的函数并选择gzip格式,然后摆脱手动构建的gzip头文件和尾文件,因为deflate()会为您完成这项工作。
我推荐使用 c)。

1
非常感谢您的解决方案。我无法弄清楚如何告诉miniz使用gzip格式,在miniz源代码中,实际上有一条注释说它不支持gzip头。您的建议b) 对我来说最好,我已经更新了上面的解决方案并接受了您的答案。 - Wen-Hsin Hsieh

0
通过上面的问题和答案,我能够编写出可运行的代码。
#include "miniz_cpp.hpp" //https://github.com/nyq/miniz-cpp/tree/master
                         //based on miniz 3.0.2
using namespace miniz_cpp;
void Save_gz_file(std::string sFileName, std::string &data)
{
    std::string sDeflated = "";
    unsigned long iDeflated = data.length()*2+18;
    sDeflated.reserve(iDeflated);
    sDeflated.append("\x1f\x8b\x08\0\0\0\0\0\0\xff", 10);

    mz_stream stream;
    memset(&stream, 0, sizeof(stream));

    stream.next_in = (unsigned char *) data.data();
    stream.avail_in = (mz_uint32) data.length();
    stream.next_out = (unsigned char *) (sDeflated.data() + 10);
    stream.avail_out = (mz_uint32) iDeflated;

    int result = mz_deflateInit2(&stream, MZ_BEST_COMPRESSION,
                 MZ_DEFLATED, -MZ_DEFAULT_WINDOW_BITS, 9,
                 MZ_DEFAULT_STRATEGY);

    if(result == MZ_OK)
    {
        result = mz_deflate(&stream, MZ_FINISH);
        if(result == MZ_STREAM_END) 
        {
            std::ofstream some_file(sFileName);
            some_file.write(sDeflated.data(), stream.total_out + 10); 
            uLong crc = mz_crc32(MZ_CRC32_INIT, (mz_uint8*) (data.data()),
                                 data.length());
            some_file.write((const char*) &crc, 4);
            uLong len = data.length();
            some_file.write((const char*) &len, 4);                            
        }
        mz_deflateEnd(&stream);   
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接