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

Cuando se busca cómo inflar datos comprimidos con gzip en iOS, el siguiente método aparece 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 (deflactados en una máquina Linux con el módulo gzip de Python) que este método que ejecuta en iOS no se está inflando. Esto es lo que está pasando:

En la última iteración del ciclo while, inflate () devuelve Z_BUF_ERROR y se sale del ciclo. Pero inflateEnd (), que se llama después del bucle, devuelve Z_OK. El código luego asume 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 pruebas 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ó de inflar la última parte de los datos.

No sé mucho acerca de la compresión y cómo funciona gzip, así que no me atrevo a realizar cambios en este código sin entender completamente lo que hace. Espero que alguien con más conocimiento sobre el tema pueda arrojar algo de luz sobre esta falla lógica potencial en el código anterior, y sugiera una manera de solucionarlo.

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

Editar:

Por lo tanto, es un error! Ahora, ¿cómo lo arreglamos? A continuación se muestra 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; } 

Edición 2:

Aquí hay un ejemplo de proyecto de Xcode que ilustra el problema en el que me estoy ejecutando. El desinflado ocurre en el lado del servidor y los datos son base64 y url codificados antes de ser transportados a través de HTTP. He incrustado la cadena base64 codificada en url en el ViewController.m. Los métodos de deencoding url y deencoding base64, así como sus métodos gzipInflate están en NSDataExtension.m

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

Aquí está el archivo binario tal como está deflactado por la biblioteca gzip de python:

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

Esta es la cadena base64 codificada de la URL que se transporta a través de 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 ha completado la inflación. inflateEnd() devolver Z_OK realmente no significa mucho, solo que se le dio un estado válido y pudo liberar la memoria.

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 rendirse. En ese caso, simplemente llame a inflate() nuevamente con más entrada o más espacio de salida. Luego obtendrás la Z_STREAM_END .

De la documentación 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:

Como hay un código de buggy flotando por ahí, a continuación se muestra el código adecuado para implementar el método deseado. Este código maneja flujos de gzip incompletos, flujos de gzip concatenados y flujos de gzip muy grandes. Para flujos de gzip muy grandes, las longitudes unsigned en z_stream no son lo suficientemente grandes cuando se comstackn como un ejecutable de 64 bits. NSUInteger es de 64 bits, mientras que unsigned es de 32 bits. En ese caso, tiene que hacer un bucle en la entrada para alimentarla a inflate() .

Este ejemplo simplemente devuelve nil en cualquier error. La naturaleza del error se indica en un comentario después de cada return nil; , en caso de que se desee un manejo de 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 anotado del sitio zlib , Z_BUF_ERROR es solo una indicación de que no hay más salida a menos que inflate () tenga más entrada, no en sí misma una razón para abortar el bucle de inflado de manera anormal.

De hecho, la muestra enlazada parece manejar Z_BUF_ERROR exactamente como Z_OK .