Ben Copsey

Move zlib stuff into new classes

  1 +//
  2 +// ASIDataCompressor.h
  3 +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk
  10 +// You may also find it helpful if you need to deflate data and files yourself - see the class methods below
  11 +// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
  12 +
  13 +#import <Foundation/Foundation.h>
  14 +#import <zlib.h>
  15 +
  16 +@interface ASIDataCompressor : NSObject {
  17 + BOOL streamReady;
  18 + z_stream zStream;
  19 +}
  20 +
  21 +// Convenience constructor will call setupStream for you
  22 ++ (id)compressor;
  23 +
  24 +// Compress the passed chunk of data
  25 +- (NSData *)compressBytes:(Bytef *)bytes length:(NSInteger)length error:(NSError **)err;
  26 +
  27 +// Convenience method - pass it some data, and you'll get deflated data back
  28 ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;
  29 +
  30 +// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath
  31 ++ (void)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
  32 +
  33 +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor'
  34 +- (NSError *)setupStream;
  35 +
  36 +// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through
  37 +// If deflating finishes or fails, this method will be called automatically
  38 +- (NSError *)closeStream;
  39 +
  40 +@property (assign, readonly) BOOL streamReady;
  41 +@end
  1 +//
  2 +// ASIDataCompressor.m
  3 +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import "ASIDataCompressor.h"
  10 +#import "ASIHTTPRequest.h"
  11 +
  12 +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
  13 +#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION
  14 +
  15 +@interface ASIDataCompressor ()
  16 ++ (NSError *)deflateErrorWithCode:(int)code;
  17 +@end
  18 +
  19 +@implementation ASIDataCompressor
  20 +
  21 ++ (id)compressor
  22 +{
  23 + ASIDataCompressor *compressor = [[[self alloc] init] autorelease];
  24 + [compressor setupStream];
  25 + return compressor;
  26 +}
  27 +
  28 +- (void)dealloc
  29 +{
  30 + if (streamReady) {
  31 + [self closeStream];
  32 + }
  33 + [super dealloc];
  34 +}
  35 +
  36 +- (NSError *)setupStream
  37 +{
  38 + if (streamReady) {
  39 + return nil;
  40 + }
  41 + // Setup the inflate stream
  42 + zStream.zalloc = Z_NULL;
  43 + zStream.zfree = Z_NULL;
  44 + zStream.opaque = Z_NULL;
  45 + zStream.avail_in = 0;
  46 + zStream.next_in = 0;
  47 + int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
  48 + if (status != Z_OK) {
  49 + return [[self class] deflateErrorWithCode:status];
  50 + }
  51 + streamReady = YES;
  52 + return nil;
  53 +}
  54 +
  55 +- (NSError *)closeStream
  56 +{
  57 + if (!streamReady) {
  58 + return nil;
  59 + }
  60 + // Close the deflate stream
  61 + streamReady = NO;
  62 + int status = deflateEnd(&zStream);
  63 + if (status != Z_OK) {
  64 + return [[self class] deflateErrorWithCode:status];
  65 + }
  66 + return nil;
  67 +}
  68 +
  69 +- (NSData *)compressBytes:(Bytef *)bytes length:(NSInteger)length error:(NSError **)err
  70 +{
  71 + if (length == 0) return nil;
  72 +
  73 + NSUInteger halfLength = length/2;
  74 +
  75 + // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below
  76 + NSMutableData *outputData = [NSMutableData dataWithLength:length/2];
  77 +
  78 + int status;
  79 +
  80 + zStream.next_in = bytes;
  81 + zStream.avail_in = length;
  82 +
  83 + NSInteger bytesProcessedAlready = zStream.total_out;
  84 + while (zStream.avail_out == 0) {
  85 +
  86 + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
  87 + [outputData increaseLengthBy:halfLength];
  88 + }
  89 +
  90 + zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
  91 + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
  92 +
  93 + status = deflate(&zStream, Z_FINISH);
  94 +
  95 + if (status == Z_STREAM_END) {
  96 + [self closeStream];
  97 + } else if (status != Z_OK) {
  98 + *err = [[self class] deflateErrorWithCode:status];
  99 + [self closeStream];
  100 + return NO;
  101 + }
  102 + }
  103 +
  104 + // Set real length
  105 + [outputData setLength: zStream.total_out-bytesProcessedAlready];
  106 + return outputData;
  107 +}
  108 +
  109 +
  110 ++ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
  111 +{
  112 + NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:err];
  113 + if (*err) {
  114 + return nil;
  115 + }
  116 + return outputData;
  117 +}
  118 +
  119 +
  120 +
  121 ++ (void)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
  122 +{
  123 + // Create an empty file at the destination path
  124 + if (![[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
  125 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
  126 + return;
  127 + }
  128 +
  129 + // Ensure the source file exists
  130 + if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {
  131 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
  132 + return;
  133 + }
  134 +
  135 + UInt8 inputData[DATA_CHUNK_SIZE];
  136 + NSData *outputData;
  137 + int readLength;
  138 +
  139 + ASIDataCompressor *compressor = [ASIDataCompressor compressor];
  140 +
  141 + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
  142 + [inputStream open];
  143 + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
  144 + [outputStream open];
  145 +
  146 + while ([compressor streamReady]) {
  147 +
  148 + // Read some data from the file
  149 + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
  150 +
  151 + // Make sure nothing went wrong
  152 + if ([inputStream streamError]) {
  153 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
  154 + [compressor closeStream];
  155 + return;
  156 + }
  157 +
  158 + // Attempt to inflate the chunk of data
  159 + outputData = [compressor compressBytes:inputData length:readLength error:err];
  160 + if (*err) {
  161 + return;
  162 + }
  163 +
  164 + // Write the deflated data out to the destination file
  165 + [outputStream write:[outputData bytes] maxLength:[outputData length]];
  166 +
  167 + // Make sure nothing went wrong
  168 + if ([outputStream streamError]) {
  169 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
  170 + [compressor closeStream];
  171 + return;
  172 + }
  173 +
  174 + }
  175 +
  176 + [inputStream close];
  177 + [outputStream close];
  178 +}
  179 +
  180 ++ (NSError *)deflateErrorWithCode:(int)code
  181 +{
  182 + return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
  183 +}
  184 +
  185 +@synthesize streamReady;
  186 +@end
  1 +//
  2 +// ASIDataDecompressor.h
  3 +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk
  10 +// You may also find it helpful if you need to inflate data and files yourself - see the class methods below
  11 +
  12 +
  13 +#import <Foundation/Foundation.h>
  14 +#import <zlib.h>
  15 +
  16 +@interface ASIDataDecompressor : NSObject {
  17 + BOOL streamReady;
  18 + z_stream zStream;
  19 +}
  20 +
  21 +// Convenience constructor will call setupStream for you
  22 ++ (id)decompressor;
  23 +
  24 +// Uncompress the passed chunk of data
  25 +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSInteger)length error:(NSError **)err;
  26 +
  27 +// Convenience method - pass it some deflated data, and you'll get inflated data back
  28 ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err;
  29 +
  30 +// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath
  31 ++ (void)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
  32 +
  33 +// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor'
  34 +- (NSError *)setupStream;
  35 +
  36 +// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through
  37 +// If inflating finishes or fails, this method will be called automatically
  38 +- (NSError *)closeStream;
  39 +
  40 +@property (assign, readonly) BOOL streamReady;
  41 +@end
  1 +//
  2 +// ASIDataDecompressor.m
  3 +// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import "ASIDataDecompressor.h"
  10 +#import "ASIHTTPRequest.h"
  11 +
  12 +#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
  13 +
  14 +@interface ASIDataDecompressor ()
  15 ++ (NSError *)inflateErrorWithCode:(int)code;
  16 +@end;
  17 +
  18 +@implementation ASIDataDecompressor
  19 +
  20 ++ (id)decompressor
  21 +{
  22 + ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease];
  23 + [decompressor setupStream];
  24 + return decompressor;
  25 +}
  26 +
  27 +- (void)dealloc
  28 +{
  29 + if (streamReady) {
  30 + [self closeStream];
  31 + }
  32 + [super dealloc];
  33 +}
  34 +
  35 +- (NSError *)setupStream
  36 +{
  37 + if (streamReady) {
  38 + return nil;
  39 + }
  40 + // Setup the inflate stream
  41 + zStream.zalloc = Z_NULL;
  42 + zStream.zfree = Z_NULL;
  43 + zStream.opaque = Z_NULL;
  44 + zStream.avail_in = 0;
  45 + zStream.next_in = 0;
  46 + int status = inflateInit2(&zStream, (15+32));
  47 + if (status != Z_OK) {
  48 + return [[self class] inflateErrorWithCode:status];
  49 + }
  50 + streamReady = YES;
  51 + return nil;
  52 +}
  53 +
  54 +- (NSError *)closeStream
  55 +{
  56 + if (!streamReady) {
  57 + return nil;
  58 + }
  59 + // Close the inflate stream
  60 + streamReady = NO;
  61 + int status = inflateEnd(&zStream);
  62 + if (status != Z_OK) {
  63 + return [[self class] inflateErrorWithCode:status];
  64 + }
  65 + return nil;
  66 +}
  67 +
  68 +- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSInteger)length error:(NSError **)err
  69 +{
  70 + if (length == 0) return nil;
  71 +
  72 + NSUInteger halfLength = length/2;
  73 + NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength];
  74 +
  75 + int status;
  76 +
  77 + zStream.next_in = bytes;
  78 + zStream.avail_in = length;
  79 +
  80 + NSInteger bytesProcessedAlready = zStream.total_out;
  81 + while (zStream.avail_out == 0) {
  82 +
  83 + if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
  84 + [outputData increaseLengthBy:halfLength];
  85 + }
  86 +
  87 + zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
  88 + zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
  89 +
  90 + status = inflate(&zStream, Z_SYNC_FLUSH);
  91 +
  92 + if (status == Z_STREAM_END) {
  93 + *err = [self closeStream];
  94 + if (*err) {
  95 + return nil;
  96 + }
  97 + } else if (status != Z_OK) {
  98 + *err = [[self class] inflateErrorWithCode:status];
  99 + [self closeStream];
  100 + return nil;
  101 + }
  102 + }
  103 +
  104 + // Set real length
  105 + [outputData setLength: zStream.total_out-bytesProcessedAlready];
  106 + return outputData;
  107 +}
  108 +
  109 +
  110 ++ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err
  111 +{
  112 + NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:err];
  113 + if (*err) {
  114 + return nil;
  115 + }
  116 + return outputData;
  117 +}
  118 +
  119 ++ (void)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
  120 +{
  121 + // Create an empty file at the destination path
  122 + if (![[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
  123 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
  124 + return;
  125 + }
  126 +
  127 + // Ensure the source file exists
  128 + if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {
  129 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
  130 + return;
  131 + }
  132 +
  133 + UInt8 inputData[DATA_CHUNK_SIZE];
  134 + NSData *outputData;
  135 + int readLength;
  136 +
  137 + ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor];
  138 +
  139 + NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
  140 + [inputStream open];
  141 + NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
  142 + [outputStream open];
  143 +
  144 + while ([decompressor streamReady]) {
  145 +
  146 + // Read some data from the file
  147 + readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
  148 +
  149 + // Make sure nothing went wrong
  150 + if ([inputStream streamError]) {
  151 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
  152 + [decompressor closeStream];
  153 + return;
  154 + }
  155 +
  156 + // Attempt to inflate the chunk of data
  157 + outputData = [decompressor uncompressBytes:inputData length:readLength error:err];
  158 + if (*err) {
  159 + return;
  160 + }
  161 +
  162 + // Write the inflated data out to the destination file
  163 + [outputStream write:[outputData bytes] maxLength:[outputData length]];
  164 +
  165 + // Make sure nothing went wrong
  166 + if ([outputStream streamError]) {
  167 + *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
  168 + [decompressor closeStream];
  169 + return;
  170 + }
  171 +
  172 + }
  173 +
  174 + [inputStream close];
  175 + [outputStream close];
  176 +}
  177 +
  178 +
  179 ++ (NSError *)inflateErrorWithCode:(int)code
  180 +{
  181 + return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %hi",code],NSLocalizedDescriptionKey,nil]];
  182 +}
  183 +
  184 +@synthesize streamReady;
  185 +@end
@@ -14,13 +14,15 @@ @@ -14,13 +14,15 @@
14 #if TARGET_OS_IPHONE 14 #if TARGET_OS_IPHONE
15 #import <CFNetwork/CFNetwork.h> 15 #import <CFNetwork/CFNetwork.h>
16 #endif 16 #endif
17 -#import <zlib.h> 17 +
18 #import <stdio.h> 18 #import <stdio.h>
19 #import "ASIHTTPRequestConfig.h" 19 #import "ASIHTTPRequestConfig.h"
20 #import "ASIHTTPRequestDelegate.h" 20 #import "ASIHTTPRequestDelegate.h"
21 #import "ASIProgressDelegate.h" 21 #import "ASIProgressDelegate.h"
22 #import "ASICacheDelegate.h" 22 #import "ASICacheDelegate.h"
23 23
  24 +@class ASIDataDecompressor;
  25 +
24 extern NSString *ASIHTTPRequestVersion; 26 extern NSString *ASIHTTPRequestVersion;
25 27
26 // Make targeting different platforms more reliable 28 // Make targeting different platforms more reliable
@@ -54,7 +56,8 @@ typedef enum _ASINetworkErrorType { @@ -54,7 +56,8 @@ typedef enum _ASINetworkErrorType {
54 ASIInternalErrorWhileApplyingCredentialsType = 7, 56 ASIInternalErrorWhileApplyingCredentialsType = 7,
55 ASIFileManagementError = 8, 57 ASIFileManagementError = 8,
56 ASITooMuchRedirectionErrorType = 9, 58 ASITooMuchRedirectionErrorType = 9,
57 - ASIUnhandledExceptionError = 10 59 + ASIUnhandledExceptionError = 10,
  60 + ASICompressionError = 11
58 61
59 } ASINetworkErrorType; 62 } ASINetworkErrorType;
60 63
@@ -149,7 +152,7 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -149,7 +152,7 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
149 // The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath 152 // The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath
150 NSString *temporaryFileDownloadPath; 153 NSString *temporaryFileDownloadPath;
151 154
152 - // If 155 + // If the response is gzipped and shouldWaitToInflateCompressedResponses is NO, a file will be created at this path containing the inflated response as it comes in
153 NSString *temporaryUncompressedDataDownloadPath; 156 NSString *temporaryUncompressedDataDownloadPath;
154 157
155 // Used for writing data to a file when downloadDestinationPath is set 158 // Used for writing data to a file when downloadDestinationPath is set
@@ -430,12 +433,24 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -430,12 +433,24 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
430 // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache 433 // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache
431 NSTimeInterval secondsToCache; 434 NSTimeInterval secondsToCache;
432 435
433 - z_stream zStream; 436 + // When downloading a gzipped response, the request will use this helper object to inflate the response
434 - NSMutableData *uncompressedResponseData; 437 + ASIDataDecompressor *dataDecompressor;
435 - int lastLen; 438 +
436 - 439 + // Controls how responses with a gzipped encoding are inflated (decompressed)
  440 + // When set to YES (This is the default):
  441 + // * gzipped responses for requests without a downloadDestinationPath will be inflated only when [request responseData] / [request responseString] is called
  442 + // * gzipped responses for requests with a downloadDestinationPath set will be inflated only when the request completes
  443 + //
  444 + // When set to NO
  445 + // All requests will inflate the response as it comes in
  446 + // * If the request has no downloadDestinationPath set, the raw (compressed) response is disgarded and rawResponseData will contain the decompressed response
  447 + // * If the request has a downloadDestinationPath, the raw response will be stored in temporaryFileDownloadPath as normal, the inflated response will be stored in temporaryUncompressedDataDownloadPath
  448 + // Once the request completes suceessfully, the contents of temporaryUncompressedDataDownloadPath are moved into downloadDestinationPath
  449 + //
  450 + // Setting this to NO may be especially useful for users using ASIHTTPRequest in conjunction with a streaming parser, as it will allow partial gzipped responses to be inflated and passed on to the parser while the request is still running
  451 + BOOL shouldWaitToInflateCompressedResponses;
  452 +
437 } 453 }
438 -- (NSData *)uncompressBytes:(void *)bytes length:(NSInteger)length error:(NSError **)err;  
439 454
440 #pragma mark init / dealloc 455 #pragma mark init / dealloc
441 456
@@ -664,24 +679,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -664,24 +679,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
664 // Dump all session data (authentication and cookies) 679 // Dump all session data (authentication and cookies)
665 + (void)clearSession; 680 + (void)clearSession;
666 681
667 -#pragma mark gzip decompression  
668 -  
669 -// Uncompress gzipped data with zlib  
670 -+ (NSData *)uncompressZippedData:(NSData*)compressedData error:(NSError **)err;  
671 -  
672 -// Uncompress gzipped data from a file into another file, used when downloading to a file  
673 -+ (BOOL)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;  
674 -+ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest;  
675 -  
676 -#pragma mark gzip compression  
677 -  
678 -// Compress data with gzip using zlib  
679 -+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;  
680 -  
681 -// gzip compress data from a file, saving to another file, used for uploading when shouldCompressRequestBody is true  
682 -+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;  
683 -+ (int)compressDataFromSource:(FILE *)source toDestination:(FILE *)dest;  
684 -  
685 #pragma mark get user agent 682 #pragma mark get user agent
686 683
687 // Will be used as a user agent if requests do not specify a custom user agent 684 // Will be used as a user agent if requests do not specify a custom user agent
@@ -865,4 +862,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -865,4 +862,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
865 @property (assign) ASICacheStoragePolicy cacheStoragePolicy; 862 @property (assign) ASICacheStoragePolicy cacheStoragePolicy;
866 @property (assign, readonly) BOOL didUseCachedResponse; 863 @property (assign, readonly) BOOL didUseCachedResponse;
867 @property (assign) NSTimeInterval secondsToCache; 864 @property (assign) NSTimeInterval secondsToCache;
  865 +@property (retain) ASIDataDecompressor *dataDecompressor;
  866 +@property (assign) BOOL shouldWaitToInflateCompressedResponses;
868 @end 867 @end
This diff is collapsed. Click to expand it.
  1 +//
  2 +// ASIDataCompressorTests.h
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import <Foundation/Foundation.h>
  10 +#import "ASITestCase.h"
  11 +
  12 +@interface ASIDataCompressorTests : ASITestCase {
  13 +
  14 +}
  15 +
  16 +@end
  1 +//
  2 +// ASIDataCompressorTests.m
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 17/08/2010.
  6 +// Copyright 2010 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +// Sadly these tests only work on Mac because of the dependency on NSTask, but I'm fairly sure this class should behave in the same way on iOS
  9 +
  10 +#import "ASIDataCompressorTests.h"
  11 +#import "ASIDataCompressor.h"
  12 +#import "ASIDataDecompressor.h"
  13 +#import "ASIHTTPRequest.h"
  14 +
  15 +@implementation ASIDataCompressorTests
  16 +
  17 +
  18 +- (void)testInflateData
  19 +{
  20 + // Test in-memory inflate using uncompressData:error:
  21 + NSUInteger i;
  22 +
  23 + NSString *originalString = [NSString string];
  24 + for (i=0; i<1000; i++) {
  25 + originalString = [originalString stringByAppendingFormat:@"This is line %i\r\n",i];
  26 + }
  27 +
  28 + NSError *error = nil;
  29 + NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
  30 + NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"];
  31 + [ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
  32 + if (error) {
  33 + GHFail(@"Failed to remove old file, cannot proceed with test");
  34 + }
  35 + [originalString writeToFile:filePath atomically:NO encoding:NSUTF8StringEncoding error:&error];
  36 + if (error) {
  37 + GHFail(@"Failed to write string, cannot proceed with test");
  38 + }
  39 +
  40 + NSTask *task = [[[NSTask alloc] init] autorelease];
  41 + [task setLaunchPath:@"/usr/bin/gzip"];
  42 + [task setArguments:[NSArray arrayWithObject:filePath]];
  43 + [task launch];
  44 + [task waitUntilExit];
  45 +
  46 + NSData *deflatedData = [NSData dataWithContentsOfFile:gzippedFilePath];
  47 +
  48 + NSData *inflatedData = [ASIDataDecompressor uncompressData:deflatedData error:&error];
  49 + if (error) {
  50 + GHFail(@"Inflate failed because %@",error);
  51 + }
  52 +
  53 + NSString *inflatedString = [[[NSString alloc] initWithBytes:[inflatedData bytes] length:[inflatedData length] encoding:NSUTF8StringEncoding] autorelease];
  54 +
  55 +
  56 + BOOL success = [inflatedString isEqualToString:originalString];
  57 + GHAssertTrue(success,@"inflated data is not the same as original");
  58 +
  59 + // Test file to file inflate
  60 + NSString *inflatedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"inflated_file.txt"];
  61 + [ASIHTTPRequest removeFileAtPath:inflatedFilePath error:&error];
  62 + if (error) {
  63 + GHFail(@"Failed to remove old file, cannot proceed with test");
  64 + }
  65 +
  66 + [ASIDataDecompressor uncompressDataFromFile:gzippedFilePath toFile:inflatedFilePath error:&error];
  67 + if (error) {
  68 + GHFail(@"Inflate failed because %@",error);
  69 + }
  70 +
  71 + originalString = [NSString stringWithContentsOfFile:inflatedFilePath encoding:NSUTF8StringEncoding error:&error];
  72 + if (error) {
  73 + GHFail(@"Failed to read the inflated data, cannot proceed with test");
  74 + }
  75 +
  76 + success = [inflatedString isEqualToString:originalString];
  77 + GHAssertTrue(success,@"inflated data is not the same as original");
  78 +
  79 +}
  80 +
  81 +- (void)testDeflateData
  82 +{
  83 + // Test in-memory deflate using compressData:error:
  84 + NSUInteger i;
  85 +
  86 + NSString *originalString = [NSString string];
  87 + for (i=0; i<1000; i++) {
  88 + originalString = [originalString stringByAppendingFormat:@"This is line %i\r\n",i];
  89 + }
  90 + NSError *error = nil;
  91 + NSData *deflatedData = [ASIDataCompressor compressData:[originalString dataUsingEncoding:NSUTF8StringEncoding] error:&error];
  92 + if (error) {
  93 + GHFail(@"Failed to deflate the data");
  94 + }
  95 +
  96 + NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"];
  97 + [ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
  98 + if (error) {
  99 + GHFail(@"Failed to remove old file, cannot proceed with test");
  100 + }
  101 +
  102 + [deflatedData writeToFile:gzippedFilePath options:0 error:&error];
  103 + if (error) {
  104 + GHFail(@"Failed to write data, cannot proceed with test");
  105 + }
  106 +
  107 + NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
  108 +
  109 + NSTask *task = [[[NSTask alloc] init] autorelease];
  110 + [task setLaunchPath:@"/usr/bin/gzip"];
  111 + [task setArguments:[NSArray arrayWithObjects:@"-d",gzippedFilePath,nil]];
  112 + [task launch];
  113 + [task waitUntilExit];
  114 +
  115 + NSString *inflatedString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
  116 + if (error) {
  117 + GHFail(@"Failed to read the inflated data, cannot proceed with test");
  118 + }
  119 +
  120 + BOOL success = [inflatedString isEqualToString:originalString];
  121 + GHAssertTrue(success,@"inflated data is not the same as original");
  122 +
  123 +
  124 + // Test file to file deflate
  125 + [ASIHTTPRequest removeFileAtPath:gzippedFilePath error:&error];
  126 +
  127 + [ASIDataCompressor compressDataFromFile:filePath toFile:gzippedFilePath error:&error];
  128 + if (error) {
  129 + GHFail(@"Deflate failed because %@",error);
  130 + }
  131 + [ASIHTTPRequest removeFileAtPath:filePath error:&error];
  132 +
  133 + task = [[[NSTask alloc] init] autorelease];
  134 + [task setLaunchPath:@"/usr/bin/gzip"];
  135 + [task setArguments:[NSArray arrayWithObjects:@"-d",gzippedFilePath,nil]];
  136 + [task launch];
  137 + [task waitUntilExit];
  138 +
  139 + inflatedString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
  140 +
  141 + success = ([inflatedString isEqualToString:originalString]);
  142 + GHAssertTrue(success,@"deflate data is not the same as that generated by gzip");
  143 +
  144 +}
  145 +
  146 +@end
@@ -47,7 +47,6 @@ @@ -47,7 +47,6 @@
47 - (void)testTooMuchRedirection; 47 - (void)testTooMuchRedirection;
48 - (void)testRedirectToNewDomain; 48 - (void)testRedirectToNewDomain;
49 - (void)test303Redirect; 49 - (void)test303Redirect;
50 -- (void)testCompression;  
51 - (void)testSubclass; 50 - (void)testSubclass;
52 - (void)testTimeOutWithoutDownloadDelegate; 51 - (void)testTimeOutWithoutDownloadDelegate;
53 - (void)testThrottlingDownloadBandwidth; 52 - (void)testThrottlingDownloadBandwidth;
@@ -636,21 +636,30 @@ @@ -636,21 +636,30 @@
636 - (void)testCompressedResponseDownloadToFile 636 - (void)testCompressedResponseDownloadToFile
637 { 637 {
638 NSString *path = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"testfile"]; 638 NSString *path = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"testfile"];
639 - 639 +
640 NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease]; 640 NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
641 ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; 641 ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
642 [request setDownloadDestinationPath:path]; 642 [request setDownloadDestinationPath:path];
643 [request startSynchronous]; 643 [request startSynchronous];
644 - 644 +
645 NSString *tempPath = [request temporaryFileDownloadPath]; 645 NSString *tempPath = [request temporaryFileDownloadPath];
646 GHAssertNil(tempPath,@"Failed to clean up temporary download file"); 646 GHAssertNil(tempPath,@"Failed to clean up temporary download file");
647 - 647 +
648 - //BOOL success = (![[NSFileManager defaultManager] fileExistsAtPath:tempPath]); 648 + BOOL success = [[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL] isEqualToString:@"This is the expected content for the first string"];
649 - //GHAssertTrue(success,@"Failed to remove file from temporary location"); 649 + GHAssertTrue(success,@"Failed to download data to a file");
650 - 650 +
651 - BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path] encoding:NSUTF8StringEncoding error:NULL] isEqualToString:@"This is the expected content for the first string"]; 651 + // Now test with inflating the response on the fly
  652 + [ASIHTTPRequest removeFileAtPath:path error:NULL];
  653 + request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
  654 + [request setDownloadDestinationPath:path];
  655 + [request setShouldWaitToInflateCompressedResponses:NO];
  656 + [request startSynchronous];
  657 +
  658 + tempPath = [request temporaryFileDownloadPath];
  659 + GHAssertNil(tempPath,@"Failed to clean up temporary download file");
  660 +
  661 + success = [[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL] isEqualToString:@"This is the expected content for the first string"];
652 GHAssertTrue(success,@"Failed to download data to a file"); 662 GHAssertTrue(success,@"Failed to download data to a file");
653 -  
654 } 663 }
655 664
656 665
@@ -1171,18 +1180,25 @@ @@ -1171,18 +1180,25 @@
1171 NSString *encoding = [[request responseHeaders] objectForKey:@"Content-Encoding"]; 1180 NSString *encoding = [[request responseHeaders] objectForKey:@"Content-Encoding"];
1172 BOOL success = (!encoding || [encoding rangeOfString:@"gzip"].location != NSNotFound); 1181 BOOL success = (!encoding || [encoding rangeOfString:@"gzip"].location != NSNotFound);
1173 GHAssertTrue(success,@"Got incorrect request headers from server"); 1182 GHAssertTrue(success,@"Got incorrect request headers from server");
1174 - 1183 +
1175 success = ([request rawResponseData] == [request responseData]); 1184 success = ([request rawResponseData] == [request responseData]);
1176 GHAssertTrue(success,@"Attempted to uncompress data that was not compressed"); 1185 GHAssertTrue(success,@"Attempted to uncompress data that was not compressed");
1177 - 1186 +
1178 url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease]; 1187 url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
1179 request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; 1188 request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
1180 [request startSynchronous]; 1189 [request startSynchronous];
1181 success = ([request rawResponseData] != [request responseData]); 1190 success = ([request rawResponseData] != [request responseData]);
1182 GHAssertTrue(success,@"Uncompressed data is the same as compressed data"); 1191 GHAssertTrue(success,@"Uncompressed data is the same as compressed data");
1183 - 1192 +
1184 success = [[request responseString] isEqualToString:@"This is the expected content for the first string"]; 1193 success = [[request responseString] isEqualToString:@"This is the expected content for the first string"];
1185 GHAssertTrue(success,@"Failed to decompress data correctly?"); 1194 GHAssertTrue(success,@"Failed to decompress data correctly?");
  1195 +
  1196 + url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
  1197 + request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
  1198 + [request setShouldWaitToInflateCompressedResponses:NO];
  1199 + [request startSynchronous];
  1200 + success = ([request rawResponseData] == [request responseData]);
  1201 + GHAssertTrue(success,@"Failed to populate rawResponseData with the inflated data");
1186 } 1202 }
1187 1203
1188 1204
@@ -1284,54 +1300,26 @@ @@ -1284,54 +1300,26 @@
1284 GHAssertTrue(success,@"Failed to use GET on new URL"); 1300 GHAssertTrue(success,@"Failed to use GET on new URL");
1285 } 1301 }
1286 1302
1287 -- (void)testCompression 1303 +- (void)testCompressedBody
1288 { 1304 {
  1305 +
1289 NSString *content = @"This is the test content. This is the test content. This is the test content. This is the test content."; 1306 NSString *content = @"This is the test content. This is the test content. This is the test content. This is the test content.";
1290 1307
1291 // Test in memory compression / decompression 1308 // Test in memory compression / decompression
1292 NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; 1309 NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
1293 - NSData *compressedData = [ASIHTTPRequest compressData:data]; 1310 +
1294 - NSData *uncompressedData = [ASIHTTPRequest uncompressZippedData:compressedData];  
1295 - NSString *newContent = [[[NSString alloc] initWithBytes:[uncompressedData bytes] length:[uncompressedData length] encoding:NSUTF8StringEncoding] autorelease];  
1296 -  
1297 - BOOL success = [newContent isEqualToString:content];  
1298 - GHAssertTrue(success,@"Failed compress or decompress the correct data");  
1299 -  
1300 - // Test file to file compression / decompression  
1301 -  
1302 - NSString *basePath = [self filePathForTemporaryTestFiles];  
1303 - NSString *sourcePath = [basePath stringByAppendingPathComponent:@"text.txt"];  
1304 - NSString *destPath = [basePath stringByAppendingPathComponent:@"text.txt.compressed"];  
1305 - NSString *newPath = [basePath stringByAppendingPathComponent:@"text2.txt"];  
1306 -  
1307 - [content writeToFile:sourcePath atomically:NO encoding:NSUTF8StringEncoding error:NULL];  
1308 - [ASIHTTPRequest compressDataFromFile:sourcePath toFile:destPath];  
1309 - [ASIHTTPRequest uncompressZippedDataFromFile:destPath toFile:newPath];  
1310 - success = [[NSString stringWithContentsOfFile:newPath encoding:NSUTF8StringEncoding error:NULL] isEqualToString:content];  
1311 - GHAssertTrue(success,@"Failed compress or decompress the correct data");  
1312 1311
1313 - // Test compressed body  
1314 - // Body is deflated by ASIHTTPRequest, sent, inflated by the server, printed, deflated by mod_deflate, response is inflated by ASIHTTPRequest  
1315 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/compressed_post_body"]]; 1312 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/compressed_post_body"]];
1316 [request setRequestMethod:@"PUT"]; 1313 [request setRequestMethod:@"PUT"];
1317 [request setShouldCompressRequestBody:YES]; 1314 [request setShouldCompressRequestBody:YES];
1318 - [request appendPostData:data];  
1319 - [request startSynchronous];  
1320 -  
1321 - success = [[request responseString] isEqualToString:content];  
1322 - GHAssertTrue(success,@"Failed to compress the body, or server failed to decompress it");  
1323 -  
1324 - request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/compressed_post_body"]];  
1325 - [request setRequestMethod:@"PUT"];  
1326 - [request setShouldCompressRequestBody:YES];  
1327 [request setShouldStreamPostDataFromDisk:YES]; 1315 [request setShouldStreamPostDataFromDisk:YES];
1328 [request setUploadProgressDelegate:self]; 1316 [request setUploadProgressDelegate:self];
1329 - [request setPostBodyFilePath:sourcePath]; 1317 + [request appendPostData:data];
1330 [request startSynchronous]; 1318 [request startSynchronous];
1331 1319
1332 - success = [[request responseString] isEqualToString:content]; 1320 + BOOL success = ([[request responseString] isEqualToString:content]);
1333 - GHAssertTrue(success,@"Failed to compress the body, or server failed to decompress it"); 1321 + GHAssertTrue(success,@"Failed to compress the body, or server failed to decompress it");
1334 - 1322 +
1335 } 1323 }
1336 1324
1337 1325
This diff was suppressed by a .gitattributes entry.