2013-07-23 44 views
6

Khi tìm kiếm trên làm thế nào để thổi phồng gzip dữ liệu nén trên iOS, các phương pháp sau đây xuất hiện trong số kết quả:Đây có phải là lỗi trong phương pháp thổi phồng gzip này không?

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

Nhưng tôi đã đi qua một số ví dụ về dữ liệu (xì hơi trên một máy Linux với Python của gzip module) rằng phương pháp này đang chạy trên iOS không thể tăng cao. Đây là những gì đang xảy ra:

Trong lần lặp cuối cùng của vòng lặp while() trả về Z_BUF_ERROR và vòng lặp được thoát. Nhưng inflateEnd(), được gọi sau vòng lặp, trả về Z_OK. Mã sau đó giả định rằng vì inflate() không bao giờ trả về Z_STREAM_END, lạm phát không thành công và trả về giá trị rỗng. Theo trang này, http://www.zlib.net/zlib_faq.html#faq05 Z_BUF_ERROR không phải là một lỗi nghiêm trọng, và các thử nghiệm của tôi với các ví dụ hạn chế cho thấy rằng dữ liệu được thổi phồng thành công nếu inflateEnd() trả về Z_OK, mặc dù cuộc gọi cuối cùng của inflate() không trả về Z_OK. Có vẻ như inflateEnd() đã hoàn thành việc tăng lượng dữ liệu cuối cùng.

Tôi không biết nhiều về nén và cách hoạt động của gzip, vì vậy tôi do dự khi thực hiện các thay đổi đối với mã này mà không hiểu đầy đủ về những gì nó thực hiện. Tôi hy vọng một người nào đó có nhiều kiến ​​thức về chủ đề này có thể làm sáng tỏ về lỗ hổng logic tiềm ẩn này trong đoạn mã trên, và đề xuất cách khắc phục nó.

Một phương pháp mà Google lần lượt lên, điều đó dường như gặp phải vấn đề tương tự có thể được tìm thấy ở đây: https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m

Edit:

Vì vậy, nó là một lỗi! Bây giờ, làm thế nào để chúng tôi sửa chữa nó? Dưới đây là nỗ lực của tôi. Đánh giá mã, có ai không?

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

Chỉnh sửa 2:

Dưới đây là một dự án mẫu Xcode minh họa vấn đề tôi đang chạy trong The deflate xảy ra ở phía máy chủ và dữ liệu là base64 và url được mã hóa trước khi được vận chuyển qua. HTTP. Tôi đã nhúng chuỗi base64 được mã hóa url trong ViewController.m. Url-decode và base64-decode cũng như phương pháp gzipInflate bạn đang ở trong NSDataExtension.m

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

Dưới đây là các tập tin nhị phân như xì hơi bởi thư viện python gzip:

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

Đây là chuỗi base64 được mã hóa URL được chuyển qua HTTP: https://dl.dropboxusercontent.com/u/38893107/gzip/urlEncodedBase64.txt

+0

Nỗ lực đi vào vòng lặp vô hạn nếu luồng gzip chưa hoàn thành. –

+0

Nhân tiện, "binary.zip" không phải là tệp zip. Nó là một tệp gzip. Tên phải là "binary.gz". –

+0

URL giải mã thành binary.zip (được gọi là binary.gz) và mã tôi đã cung cấp trong câu trả lời của tôi giải nén chính xác tệp đó thành tệp văn bản byte 221213. Tôi đã không nhìn vào mã của bạn để xem có gì sai - đó là công việc của bạn. –

Trả lời

6

Vâng, đó là lỗi.

Thực tế là nếu inflate() không trả lại Z_STREAM_END, thì bạn chưa hoàn thành lạm phát. inflateEnd() trả lại Z_OK không thực sự có ý nghĩa nhiều - chỉ là nó đã được đưa ra một trạng thái hợp lệ và có thể giải phóng bộ nhớ.

Vì vậy, inflate() cuối cùng phải trả lại Z_STREAM_END trước khi bạn có thể tuyên bố thành công. Tuy nhiên Z_BUF_ERROR không phải là lý do để từ bỏ. Trong trường hợp đó, bạn chỉ cần gọi lại inflate() với nhiều đầu vào hơn hoặc nhiều không gian đầu ra hơn. Sau đó, bạn sẽ nhận được Z_STREAM_END.

Từ các tài liệu trong 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. 
... */ 

Cập nhật:

Kể từ khi có mã lỗi nổi xung quanh ra khỏi đó, dưới đây là mã thích hợp để thực hiện các phương pháp mong muốn. Mã này xử lý các luồng gzip không đầy đủ, các luồng gzip được ghép nối và các luồng gzip rất lớn. Đối với luồng gzip rất lớn, độ dài unsigned trong z_stream không đủ lớn khi được biên dịch dưới dạng tệp thực thi 64 bit. NSUInteger là 64 bit, trong khi unsigned là 32 bit. Trong trường hợp đó, bạn phải lặp lại đầu vào để nạp nó vào inflate().

Ví dụ này chỉ trả về nil về mọi lỗi. Bản chất của lỗi được ghi chú trong một chú thích sau mỗi return nil;, trong trường hợp xử lý lỗi phức tạp hơn là mong muốn.

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

Cảm ơn Mark. Không có gì giống như một phản ứng từ chính tác giả zlib! Tôi đã cố sửa lỗi (xem câu hỏi đã chỉnh sửa ở trên nếu quan tâm) bằng cách thực hiện vòng lặp cho đến khi Z_STREAM_END được trả về. Nhưng trong ví dụ chú thích @Joachim được liên kết, vòng lặp bên trong được điều chỉnh trên strm.avail_out == 0, mà tôi không hiểu lý do đằng sau. –

+0

Sửa chữa của bạn không phải lúc nào cũng hoạt động. Đặc biệt, nó sẽ đi vào một vòng lặp vô hạn nếu được cung cấp một luồng gzip không đầy đủ. Ngoài ra, cách tiếp cận nói chung sẽ chỉ hoạt động nếu độ dài nén và không nén đủ nhỏ để vừa với loại không dấu. Và chỉ khi tổng chiều dài sẽ phù hợp với một loại dài chưa ký. Có những cách mạnh mẽ hơn để viết rằng phương pháp không phụ thuộc vào những giả định đó. Tôi sẽ thêm mã đúng vào câu trả lời của tôi. –

+0

Looping on strm.avail_out == 0, hoặc tương đương, chờ strm.avail_out! = 0, đợi tất cả dữ liệu nén có thể được tạo ra từ đầu vào nén được cung cấp. Điều đó không được thực hiện cho đến khi nó không điền vào bộ đệm đầu ra. Một ít dữ liệu nén đôi khi có thể tạo ra rất nhiều dữ liệu không nén, vì vậy bạn cần một vòng lặp để kéo tất cả những điều đó ra ngoài. –

2

Có, trông giống như một lỗi. Theo this annotated example from the zlib site, Z_BUF_ERROR chỉ là một dấu hiệu cho thấy không có đầu ra nhiều hơn trừ khi thổi phồng() được cung cấp với đầu vào nhiều hơn, không phải trong chính nó là một lý do để hủy bỏ vòng lặp thổi phồng bất thường.

Thực tế, mẫu được liên kết dường như xử lý Z_BUF_ERROR chính xác như Z_OK.

+0

Cảm ơn! Tôi đã chỉnh sửa câu hỏi với bản sửa lỗi đã cố gắng của mình. –

+0

@ subjective-c Miễn là bạn chuyển tất cả dữ liệu đầu vào cùng một lúc, tôi không thể thấy sự cố. Vòng lặp bên ngoài trong mẫu là cho "streaming", tức là nếu avail_out là 0, nó cố gắng nạp lại bộ đệm đầu vào và thử lại cho đến khi 'Z_STREAM_END' được trả về. Vì bạn đã chuyển tất cả dữ liệu cùng một lúc và không có gì để nạp tiền, tôi không thể thấy bạn có bất kỳ tùy chọn nào nhưng phải thử lại cho đến khi bạn gặp lỗi cứng hoặc 'Z_STREAM_END'. –

+0

cảm ơn vì đã xem xét nó. –

Các vấn đề liên quan