Po wyszukaniu jak nadmuchać gzip skompresowane dane na iOS następujący sposób pojawia się w liczbie wyników:Czy to błąd w tej metodzie nadpisywania 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;
}
Ale natrafiłem kilka przykładów danych (deflowanych na komputerze z systemem Linux z Pythona gzip module) że ta metoda działająca na iOS nie jest w stanie nadmuchać. Oto co się dzieje:
W ostatniej iteracji pętli while inflate() zwraca Z_BUF_ERROR, a pętla zostaje zakończona. Ale funkcja inflateEnd(), która jest wywoływana po pętli, zwraca Z_OK. Kod następnie zakłada, że ponieważ inflate() nigdy nie zwróciło Z_STREAM_END, inflacja nie powiodła się i zwraca wartość null.
Zgodnie z tą stroną http://www.zlib.net/zlib_faq.html#faq05 Z_BUF_ERROR nie jest błędem krytycznym, a moje testy z ograniczonymi przykładami pokazują, że dane są skutecznie zawyżone, jeśli funkcja inflateEnd() zwraca Z_OK, mimo że ostatnie wywołanie metody inflate() nie powróciło Z_OK. Wygląda na to, że metoda inflateEnd() zakończyła pompowanie ostatniego fragmentu danych.
Nie wiem zbyt wiele na temat kompresji i działania gzip, więc nie zamierzam wprowadzać zmian w tym kodzie bez pełnego zrozumienia, co robi. Mam nadzieję, że ktoś o większej wiedzy na ten temat może rzucić nieco światła na tę potencjalną lukę logiczną w powyższym kodzie i zasugerować sposób na jej naprawienie.
Inną metodą, że Google okazuje się, że wydaje się cierpieć z tego samego problemu można znaleźć tutaj: https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m
Edit:
Tak, to jest błąd! Teraz, jak to naprawić? Poniżej moja próba. Recenzja kodu, ktoś?
- (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;
}
Edit 2:
Oto przykładowy projekt Xcode, który ilustruje problem biegnę w deflate dzieje po stronie serwera, a dane jest base64 i url zakodowane przed przetransportowaniem przez. HTTP. Wbudowałem kodowany ciąg url base64 w ViewController.m. URL-i dekodowania base64-dekodowania jak wasze metody gzipInflate są w NSDataExtension.m
https://dl.dropboxusercontent.com/u/38893107/gzip/GZIPTEST.zip
Oto plik binarny jak deflowanej biblioteki python gzip:
https://dl.dropboxusercontent.com/u/38893107/gzip/binary.zip
Jest URL zakodowany ciągiem base64, który jest transportowany przez HTTP: https://dl.dropboxusercontent.com/u/38893107/gzip/urlEncodedBase64.txt
Próba przechodzi w nieskończoną pętlę, jeśli strumień gzip nie jest kompletny. –
Nawiasem mówiąc, "binary.zip" nie jest plikiem zip. To jest plik gzip. Nazwa powinna być "binary.gz". –
Adres URL dekoduje plik binary.zip (który powinien być nazywany binary.gz), a kod podany w odpowiedzi prawidłowo rozpakowuje go do 221213 bajtowego pliku tekstowego. Nie patrzyłem na twój kod, żeby zobaczyć, co jest nie tak - to twoja praca. –