在 iOS 上搜索如何对 gzip 压缩数据进行充气时,结果中出现以下方法:
- (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;
}
但是我遇到了一些数据示例(在使用 Python 的gzip 模块的 Linux 机器上放气),这种在 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 编码的。我在 ViewController.m 中嵌入了 url 编码的 base64 字符串。url-decode 和 base64-decode 以及您的 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