¿Esto es un error en este método de inflar gzip?

Al search cómo inflar los datos comprimidos de gzip en iOS, aparece el siguiente método en la cantidad de resultados:

- (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; } 

Pero he encontrado algunos ejemplos de datos (desinflados en una máquina Linux con el module gzip de Python) que este método que se ejecuta en iOS falla al inflarse. Esto es lo que está sucediendo:

En la última iteración del bucle while inflate (), se devuelve Z_BUF_ERROR y se sale del bucle. Pero inflateEnd (), que se llama después del ciclo, devuelve Z_OK. El código supone entonces que, dado que inflate () nunca devolvió Z_STREAM_END, la inflación falló y devuelve nulo.

Según esta página, http://www.zlib.net/zlib_faq.html#faq05 Z_BUF_ERROR no es un error fatal, y mis testings con ejemplos limitados muestran que los datos se inflan con éxito si el inflateEnd () devuelve Z_OK, aunque La última llamada de inflar () no devolvió Z_OK. Parece que el inflateEnd () terminó inflando el último fragment de datos.

No sé mucho acerca de la compression y de cómo funciona el gzip, así que estoy indeciso para hacer cambios a este código sin comprender completamente lo que hace. Espero que alguien con más conocimientos sobre el tema pueda arrojar algo de luz sobre este potencial defecto lógico en el código anterior y sugerir una forma de solucionarlo.

Otro método que aparece en Google, que parece sufrir el mismo problema, se puede encontrar aquí: https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m

Editar:

¡Entonces, es un error! Ahora, ¿cómo podemos solucionarlo? A continuación es mi bash. Revisión de código, ¿alguien?

 - (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; } 

Editar 2:

Aquí hay un proyecto de muestra de Xcode que ilustra el problema que estoy ejecutando. El desinflado ocurre en el lado del server y los datos son base64 y url codificados antes de ser transportados a través de HTTP. He embedded la cadena base64 codificada en url en ViewController.m. La url-decode y la base64-decode, así como sus methods gzipInflate están en NSDataExtension.m

https://dl.dropboxusercontent.com/u/38893107/gzip/GZIPTEST.zip

Aquí está el file binary deflated by python gzip library:

https://dl.dropboxusercontent.com/u/38893107/gzip/binary.zip

Esta es la cadena base64 codificada en URL que se transporta a través del HTTP: https://dl.dropboxusercontent.com/u/38893107/gzip/urlEncodedBase64.txt

Sí, es un error.

De hecho, es correcto que si inflate() no devuelve Z_STREAM_END , entonces no has completado la inflación. inflateEnd() devolver Z_OK realidad no significa mucho, solo que se le dio un estado válido y se pudo liberar la memory.

Por lo tanto, inflate() debe devolver Z_STREAM_END antes de poder declarar el éxito. Sin embargo, Z_BUF_ERROR no es una razón para darse por vencido. En ese caso, simplemente llama inflate() nuevamente con más input o más espacio de salida. Entonces obtendrás el Z_STREAM_END .

De la documentation en 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. ... */ 

Actualizar:

Dado que hay un código buggy flotando por ahí, a continuación se muestra el código apropiado para implementar el método deseado. Este código maneja secuencias de gzip incompletas, secuencias de gzip concatenadas y secuencias de gzip muy grandes. Para las transmisiones de gzip muy grandes, las longitudes unsigned en z_stream no son lo suficientemente grandes cuando se comstack como un ejecutable de 64 bits. NSUInteger es de 64 bits, mientras que unsigned es de 32 bits. En ese caso, debe hacer un bucle en la input para alimentarlo a inflate() .

Este ejemplo simplemente devuelve nil en cualquier error. La naturaleza del error se nota en un comentario después de cada return nil; , en caso de que se desee un event handling errores más sofisticado.

 - (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; } 

Sí, parece un error. De acuerdo con este ejemplo comentado del sitio zlib , Z_BUF_ERROR es solo una indicación de que no hay más salida a less que inflate () proporcione más input, no en sí misma una razón para abortar el ciclo de inflado de manera anómala.

De hecho, el ejemplo vinculado parece manejar Z_BUF_ERROR exactamente como Z_OK .