- (NSData *)gzipInflate
{
if ([self length] == 0) return self;
unsigned full_length = [self length];
unsigned half_length = [self length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = [self length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
while (!done)
{
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy: half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = [decompressed length] - strm.total_out;
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) done = YES;
else if (status != Z_OK) break;
}
if (inflateEnd (&strm) != Z_OK) return nil;
// Set real length.
if (done)
{
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
}
else return nil;
}
但是我遇到了一些数据的例子(在Linux机器上使用Python的gzip模块进行压缩),这个在iOS上运行的方法无法解压。具体情况如下:
在while循环的最后一次迭代中,inflate()返回Z_BUF_ERROR并退出循环。但是在循环之后调用的inflateEnd()返回Z_OK。代码随后假定由于inflate()从未返回Z_STREAM_END,因此解压失败并返回null。
根据这个页面http://www.zlib.net/zlib_faq.html#faq05,Z_BUF_ERROR不是致命错误,我的有限测试表明,如果inflateEnd()返回Z_OK,则数据成功解压缩,即使最后一次调用inflate()没有返回Z_OK。似乎inflateEnd()已经完成了最后一块数据的解压。
我对压缩和gzip的工作原理知之甚少,因此在不完全了解其功能的情况下,我犹豫是否更改此代码。 我希望了解该主题的专业人士可以阐明上述代码中潜在的逻辑缺陷,并提出修复方法。
另一种谷歌找到的方法似乎也存在同样的问题,可以在此处找到:https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m 编辑: 所以,这是一个错误!现在,我们该如何修复它? 以下是我的尝试。 有人能够审核代码吗?
- (NSData *)gzipInflate
{
if ([self length] == 0) return self;
unsigned full_length = [self length];
unsigned half_length = [self length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
int status;
z_stream strm;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = [self length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
do
{
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy: half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = [decompressed length] - strm.total_out;
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
switch (status) {
case Z_NEED_DICT:
status = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
case Z_STREAM_ERROR:
(void)inflateEnd(&strm);
return nil;
}
} while (status != Z_STREAM_END);
(void)inflateEnd (&strm);
// Set real length.
if (status == Z_STREAM_END)
{
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
}
else return nil;
}
编辑2:
这是一个示例Xcode项目,说明了我遇到的问题。压缩发生在服务器端,并且数据在通过HTTP传输之前进行base64和url编码。我已经将url编码的base64字符串嵌入ViewController.m中。url解码、base64解码以及gzipInflate方法都在NSDataExtension.m中。
https://dl.dropboxusercontent.com/u/38893107/gzip/GZIPTEST.zip
这是由Python gzip库压缩的二进制文件:https://dl.dropboxusercontent.com/u/38893107/gzip/binary.zip
这是通过HTTP传输的URL编码的Base64字符串: https://dl.dropboxusercontent.com/u/38893107/gzip/urlEncodedBase64.txt