Yes, it's a bug.
It is in fact correct that if inflate()
does not return Z_STREAM_END
, then you have not completed inflation. inflateEnd()
returning Z_OK
doesn't really mean much -- just that it was given a valid state and was able to free the memory.
So inflate()
must eventually return Z_STREAM_END
before you can declare success. However Z_BUF_ERROR
is not a reason to give up. In that case you simply call inflate()
again with more input or more output space. Then you will get the Z_STREAM_END
.
From the documentation in zlib.h:
/* ...
Z_BUF_ERROR if no progress is possible or if there was not enough room in the
output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
inflate() can be called again with more input and more output space to
continue decompressing.
... */
Update:
Since there is buggy code floating around out there, below is the proper code to implement the desired method. This code handles incomplete gzip streams, concatenated gzip streams, and very large gzip streams. For very large gzip streams, the unsigned
lengths in the z_stream
are not large enough when compiled as a 64-bit executable. NSUInteger
is 64 bits, whereas unsigned
is 32 bits. In that case, you have to loop on the input to feed it to inflate()
.
This example simply returns nil
on any error. The nature of the error is noted in a comment after each return nil;
, in case more sophisticated error handling is desired.
- (NSData *) gzipInflate
{
z_stream strm;
// Initialize input
strm.next_in = (Bytef *)[self bytes];
NSUInteger left = [self length]; // input left to decompress
if (left == 0)
return nil; // incomplete gzip stream
// Create starting space for output (guess double the input size, will grow
// if needed -- in an extreme case, could end up needing more than 1000
// times the input size)
NSUInteger space = left << 1;
if (space < left)
space = NSUIntegerMax;
NSMutableData *decompressed = [NSMutableData dataWithLength: space];
space = [decompressed length];
// Initialize output
strm.next_out = (Bytef *)[decompressed mutableBytes];
NSUInteger have = 0; // output generated so far
// Set up for gzip decoding
strm.avail_in = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
int status = inflateInit2(&strm, (15+16));
if (status != Z_OK)
return nil; // out of memory
// Decompress all of self
do {
// Allow for concatenated gzip streams (per RFC 1952)
if (status == Z_STREAM_END)
(void)inflateReset(&strm);
// Provide input for inflate
if (strm.avail_in == 0) {
strm.avail_in = left > UINT_MAX ? UINT_MAX : (unsigned)left;
left -= strm.avail_in;
}
// Decompress the available input
do {
// Allocate more output space if none left
if (space == have) {
// Double space, handle overflow
space <<= 1;
if (space < have) {
space = NSUIntegerMax;
if (space == have) {
// space was already maxed out!
(void)inflateEnd(&strm);
return nil; // output exceeds integer size
}
}
// Increase space
[decompressed setLength: space];
space = [decompressed length];
// Update output pointer (might have moved)
strm.next_out = (Bytef *)[decompressed mutableBytes] + have;
}
// Provide output space for inflate
strm.avail_out = space - have > UINT_MAX ? UINT_MAX :
(unsigned)(space - have);
have += strm.avail_out;
// Inflate and update the decompressed size
status = inflate (&strm, Z_SYNC_FLUSH);
have -= strm.avail_out;
// Bail out if any errors
if (status != Z_OK && status != Z_BUF_ERROR &&
status != Z_STREAM_END) {
(void)inflateEnd(&strm);
return nil; // invalid gzip stream
}
// Repeat until all output is generated from provided input (note
// that even if strm.avail_in is zero, there may still be pending
// output -- we're not done until the output buffer isn't filled)
} while (strm.avail_out == 0);
// Continue until all input consumed
} while (left || strm.avail_in);
// Free the memory allocated by inflateInit2()
(void)inflateEnd(&strm);
// Verify that the input is a valid gzip stream
if (status != Z_STREAM_END)
return nil; // incomplete gzip stream
// Set the actual length and return the decompressed data
[decompressed setLength: have];
return decompressed;
}