532 lines
20 KiB
Objective-C
532 lines
20 KiB
Objective-C
//
|
|
// 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
|