| // |
| // GTMNSData+zlib.m |
| // |
| // Copyright 2007-2008 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy |
| // of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| // |
| |
| #import "GTMNSData+zlib.h" |
| #import <zlib.h> |
| #import "GTMDefines.h" |
| |
| #define kChunkSize 1024 |
| |
| NSString *const GTMNSDataZlibErrorDomain = @"com.google.GTMNSDataZlibErrorDomain"; |
| NSString *const GTMNSDataZlibErrorKey = @"GTMNSDataZlibErrorKey"; |
| NSString *const GTMNSDataZlibRemainingBytesKey = @"GTMNSDataZlibRemainingBytesKey"; |
| |
| typedef enum { |
| CompressionModeZlib, |
| CompressionModeGzip, |
| CompressionModeRaw, |
| } CompressionMode; |
| |
| @interface NSData (GTMZlibAdditionsPrivate) |
| + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level |
| mode:(CompressionMode)mode |
| error:(NSError **)error; |
| + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| isRawData:(BOOL)isRawData |
| error:(NSError **)error; |
| @end |
| |
| @implementation NSData (GTMZlibAdditionsPrivate) |
| |
| + (NSData *)gtm_dataByCompressingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level |
| mode:(CompressionMode)mode |
| error:(NSError **)error { |
| if (!bytes || !length) { |
| return nil; |
| } |
| |
| #if defined(__LP64__) && __LP64__ |
| // Don't support > 32bit length for 64 bit, see note in header. |
| if (length > UINT_MAX) { |
| if (error) { |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorGreaterThan32BitsToCompress |
| userInfo:nil]; |
| } |
| return nil; |
| } |
| #endif |
| |
| if (level == Z_DEFAULT_COMPRESSION) { |
| // the default value is actually outside the range, so we have to let it |
| // through specifically. |
| } else if (level < Z_BEST_SPEED) { |
| level = Z_BEST_SPEED; |
| } else if (level > Z_BEST_COMPRESSION) { |
| level = Z_BEST_COMPRESSION; |
| } |
| |
| z_stream strm; |
| bzero(&strm, sizeof(z_stream)); |
| |
| int memLevel = 8; // the default |
| int windowBits = 15; // the default |
| switch (mode) { |
| case CompressionModeZlib: |
| // nothing to do |
| break; |
| |
| case CompressionModeGzip: |
| windowBits += 16; // enable gzip header instead of zlib header |
| break; |
| |
| case CompressionModeRaw: |
| windowBits *= -1; // Negative to mean no header. |
| break; |
| } |
| int retCode; |
| if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, |
| memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) { |
| // COV_NF_START - no real way to force this in a unittest (we guard all args) |
| if (error) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] |
| forKey:GTMNSDataZlibErrorKey]; |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorInternal |
| userInfo:userInfo]; |
| } |
| return nil; |
| // COV_NF_END |
| } |
| |
| // hint the size at 1/4 the input size |
| NSMutableData *result = [NSMutableData dataWithCapacity:(length/4)]; |
| unsigned char output[kChunkSize]; |
| |
| // setup the input |
| strm.avail_in = (unsigned int)length; |
| strm.next_in = (unsigned char*)bytes; |
| |
| // loop to collect the data |
| do { |
| // update what we're passing in |
| strm.avail_out = kChunkSize; |
| strm.next_out = output; |
| retCode = deflate(&strm, Z_FINISH); |
| if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { |
| // COV_NF_START - no real way to force this in a unittest |
| // (in inflate, we can feed bogus/truncated data to test, but an error |
| // here would be some internal issue w/in zlib, and there isn't any real |
| // way to test it) |
| if (error) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] |
| forKey:GTMNSDataZlibErrorKey]; |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorInternal |
| userInfo:userInfo]; |
| } |
| deflateEnd(&strm); |
| return nil; |
| // COV_NF_END |
| } |
| // collect what we got |
| unsigned gotBack = kChunkSize - strm.avail_out; |
| if (gotBack > 0) { |
| [result appendBytes:output length:gotBack]; |
| } |
| |
| } while (retCode == Z_OK); |
| |
| // if the loop exits, we used all input and the stream ended |
| _GTMDevAssert(strm.avail_in == 0, |
| @"thought we finished deflate w/o using all input, %u bytes left", |
| strm.avail_in); |
| _GTMDevAssert(retCode == Z_STREAM_END, |
| @"thought we finished deflate w/o getting a result of stream end, code %d", |
| retCode); |
| |
| // clean up |
| deflateEnd(&strm); |
| |
| return result; |
| } // gtm_dataByCompressingBytes:length:compressionLevel:useGzip: |
| |
| + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| isRawData:(BOOL)isRawData |
| error:(NSError **)error { |
| if (!bytes || !length) { |
| return nil; |
| } |
| |
| #if defined(__LP64__) && __LP64__ |
| // Don't support > 32bit length for 64 bit, see note in header. |
| if (length > UINT_MAX) { |
| return nil; |
| } |
| #endif |
| |
| z_stream strm; |
| bzero(&strm, sizeof(z_stream)); |
| |
| // setup the input |
| strm.avail_in = (unsigned int)length; |
| strm.next_in = (unsigned char*)bytes; |
| |
| int windowBits = 15; // 15 to enable any window size |
| if (isRawData) { |
| windowBits *= -1; // make it negative to signal no header. |
| } else { |
| windowBits += 32; // and +32 to enable zlib or gzip header detection. |
| } |
| |
| int retCode; |
| if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { |
| // COV_NF_START - no real way to force this in a unittest (we guard all args) |
| if (error) { |
| NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] |
| forKey:GTMNSDataZlibErrorKey]; |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorInternal |
| userInfo:userInfo]; |
| } |
| return nil; |
| // COV_NF_END |
| } |
| |
| // hint the size at 4x the input size |
| NSMutableData *result = [NSMutableData dataWithCapacity:(length*4)]; |
| unsigned char output[kChunkSize]; |
| |
| // loop to collect the data |
| do { |
| // update what we're passing in |
| strm.avail_out = kChunkSize; |
| strm.next_out = output; |
| retCode = inflate(&strm, Z_NO_FLUSH); |
| if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { |
| if (error) { |
| NSMutableDictionary *userInfo = |
| [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] |
| forKey:GTMNSDataZlibErrorKey]; |
| if (strm.msg) { |
| NSString *message = [NSString stringWithUTF8String:strm.msg]; |
| if (message) { |
| [userInfo setObject:message forKey:NSLocalizedDescriptionKey]; |
| } |
| } |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorInternal |
| userInfo:userInfo]; |
| } |
| inflateEnd(&strm); |
| return nil; |
| } |
| // collect what we got |
| unsigned gotBack = kChunkSize - strm.avail_out; |
| if (gotBack > 0) { |
| [result appendBytes:output length:gotBack]; |
| } |
| |
| } while (retCode == Z_OK); |
| |
| // make sure there wasn't more data tacked onto the end of a valid compressed |
| // stream. |
| if (strm.avail_in != 0) { |
| if (error) { |
| NSDictionary *userInfo = |
| [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in] |
| forKey:GTMNSDataZlibRemainingBytesKey]; |
| *error = [NSError errorWithDomain:GTMNSDataZlibErrorDomain |
| code:GTMNSDataZlibErrorDataRemaining |
| userInfo:userInfo]; |
| } |
| result = nil; |
| } |
| // the only way out of the loop was by hitting the end of the stream |
| _GTMDevAssert(retCode == Z_STREAM_END, |
| @"thought we finished inflate w/o getting a result of stream end, code %d", |
| retCode); |
| |
| // clean up |
| inflateEnd(&strm); |
| |
| return result; |
| } // gtm_dataByInflatingBytes:length:windowBits: |
| |
| @end |
| |
| |
| @implementation NSData (GTMZLibAdditions) |
| |
| + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes |
| length:(NSUInteger)length { |
| return [self gtm_dataByGzippingBytes:bytes length:length error:NULL]; |
| } // gtm_dataByGzippingBytes:length: |
| |
| + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeGzip |
| error:error]; |
| } // gtm_dataByGzippingBytes:length:error: |
| |
| + (NSData *)gtm_dataByGzippingData:(NSData *)data { |
| return [self gtm_dataByGzippingData:data error:NULL]; |
| } // gtm_dataByGzippingData: |
| |
| + (NSData *)gtm_dataByGzippingData:(NSData *)data error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeGzip |
| error:error]; |
| } // gtm_dataByGzippingData:error: |
| |
| + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level { |
| return [self gtm_dataByGzippingBytes:bytes |
| length:length |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByGzippingBytes:length:level: |
| |
| + (NSData *)gtm_dataByGzippingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level |
| error:(NSError **)error{ |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:level |
| mode:CompressionModeGzip |
| error:error]; |
| } // gtm_dataByGzippingBytes:length:level:error |
| |
| + (NSData *)gtm_dataByGzippingData:(NSData *)data |
| compressionLevel:(int)level { |
| return [self gtm_dataByGzippingData:data |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByGzippingData:level: |
| |
| + (NSData *)gtm_dataByGzippingData:(NSData *)data |
| compressionLevel:(int)level |
| error:(NSError **)error{ |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:level |
| mode:CompressionModeGzip |
| error:error]; |
| } // gtm_dataByGzippingData:level:error |
| |
| #pragma mark - |
| |
| + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length { |
| return [self gtm_dataByDeflatingBytes:bytes |
| length:length |
| error:NULL]; |
| } // gtm_dataByDeflatingBytes:length: |
| |
| + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:(NSError **)error{ |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeZlib |
| error:error]; |
| } // gtm_dataByDeflatingBytes:length:error |
| |
| + (NSData *)gtm_dataByDeflatingData:(NSData *)data { |
| return [self gtm_dataByDeflatingData:data error:NULL]; |
| } // gtm_dataByDeflatingData: |
| |
| + (NSData *)gtm_dataByDeflatingData:(NSData *)data error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeZlib |
| error:error]; |
| } // gtm_dataByDeflatingData: |
| |
| + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level { |
| return [self gtm_dataByDeflatingBytes:bytes |
| length:length |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByDeflatingBytes:length:level: |
| |
| + (NSData *)gtm_dataByDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level |
| error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:level |
| mode:CompressionModeZlib |
| error:error]; |
| } // gtm_dataByDeflatingBytes:length:level:error: |
| |
| + (NSData *)gtm_dataByDeflatingData:(NSData *)data |
| compressionLevel:(int)level { |
| return [self gtm_dataByDeflatingData:data |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByDeflatingData:level: |
| |
| + (NSData *)gtm_dataByDeflatingData:(NSData *)data |
| compressionLevel:(int)level |
| error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:level |
| mode:CompressionModeZlib |
| error:error]; |
| } // gtm_dataByDeflatingData:level:error: |
| |
| #pragma mark - |
| |
| + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length { |
| return [self gtm_dataByInflatingBytes:bytes |
| length:length |
| error:NULL]; |
| } // gtm_dataByInflatingBytes:length: |
| |
| + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:(NSError **)error { |
| return [self gtm_dataByInflatingBytes:bytes |
| length:length |
| isRawData:NO |
| error:error]; |
| } // gtm_dataByInflatingBytes:length:error: |
| |
| + (NSData *)gtm_dataByInflatingData:(NSData *)data { |
| return [self gtm_dataByInflatingData:data error:NULL]; |
| } // gtm_dataByInflatingData: |
| |
| + (NSData *)gtm_dataByInflatingData:(NSData *)data |
| error:(NSError **)error { |
| return [self gtm_dataByInflatingBytes:[data bytes] |
| length:[data length] |
| isRawData:NO |
| error:error]; |
| } // gtm_dataByInflatingData: |
| |
| #pragma mark - |
| |
| + (NSData *)gtm_dataByRawDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length { |
| return [self gtm_dataByRawDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:NULL]; |
| } // gtm_dataByRawDeflatingBytes:length: |
| |
| + (NSData *)gtm_dataByRawDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeRaw |
| error:error]; |
| } // gtm_dataByRawDeflatingBytes:length:error: |
| |
| + (NSData *)gtm_dataByRawDeflatingData:(NSData *)data { |
| return [self gtm_dataByRawDeflatingData:data error:NULL]; |
| } // gtm_dataByRawDeflatingData: |
| |
| + (NSData *)gtm_dataByRawDeflatingData:(NSData *)data error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:Z_DEFAULT_COMPRESSION |
| mode:CompressionModeRaw |
| error:error]; |
| } // gtm_dataByRawDeflatingData:error: |
| |
| + (NSData *)gtm_dataByRawDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level { |
| return [self gtm_dataByRawDeflatingBytes:bytes |
| length:length |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByRawDeflatingBytes:length:compressionLevel: |
| |
| + (NSData *)gtm_dataByRawDeflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| compressionLevel:(int)level |
| error:(NSError **)error{ |
| return [self gtm_dataByCompressingBytes:bytes |
| length:length |
| compressionLevel:level |
| mode:CompressionModeRaw |
| error:error]; |
| } // gtm_dataByRawDeflatingBytes:length:compressionLevel:error: |
| |
| + (NSData *)gtm_dataByRawDeflatingData:(NSData *)data |
| compressionLevel:(int)level { |
| return [self gtm_dataByRawDeflatingData:data |
| compressionLevel:level |
| error:NULL]; |
| } // gtm_dataByRawDeflatingData:compressionLevel: |
| |
| + (NSData *)gtm_dataByRawDeflatingData:(NSData *)data |
| compressionLevel:(int)level |
| error:(NSError **)error { |
| return [self gtm_dataByCompressingBytes:[data bytes] |
| length:[data length] |
| compressionLevel:level |
| mode:CompressionModeRaw |
| error:error]; |
| } // gtm_dataByRawDeflatingData:compressionLevel:error: |
| |
| + (NSData *)gtm_dataByRawInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length { |
| return [self gtm_dataByInflatingBytes:bytes |
| length:length |
| error:NULL]; |
| } // gtm_dataByRawInflatingBytes:length: |
| |
| + (NSData *)gtm_dataByRawInflatingBytes:(const void *)bytes |
| length:(NSUInteger)length |
| error:(NSError **)error{ |
| return [self gtm_dataByInflatingBytes:bytes |
| length:length |
| isRawData:YES |
| error:error]; |
| } // gtm_dataByRawInflatingBytes:length:error: |
| |
| + (NSData *)gtm_dataByRawInflatingData:(NSData *)data { |
| return [self gtm_dataByRawInflatingData:data |
| error:NULL]; |
| } // gtm_dataByRawInflatingData: |
| |
| + (NSData *)gtm_dataByRawInflatingData:(NSData *)data |
| error:(NSError **)error { |
| return [self gtm_dataByInflatingBytes:[data bytes] |
| length:[data length] |
| isRawData:YES |
| error:error]; |
| } // gtm_dataByRawInflatingData:error: |
| |
| @end |