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;
}
しかし、iOS で実行されているこのメソッドが膨張に失敗している(Python のgzip モジュールを使用して Linux マシンで収縮された) データの例をいくつか見つけました。何が起こっているかは次のとおりです。
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 の仕組みについてよく知らないので、このコードが何をするのかを完全に理解することなく、このコードを変更することをためらっています。このトピックについてより多くの知識を持っている人が、上記のコードのこの潜在的な論理上の欠陥に光を当て、それを修正する方法を提案してくれることを願っています.
同じ問題に悩まされていると思われる Google の別の方法は、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 プロジェクトのサンプルです。デフレートはサーバー側で行われ、データは base64 でエンコードされ、HTTP 経由で転送される前に 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