Browse Source

Added support for gzip body encoding

Pierre-Olivier Latour 11 years ago
parent
commit
06630d3245
1 changed files with 114 additions and 1 deletions
  1. 114 1
      CGDWebServer/GCDWebServerRequest.m

+ 114 - 1
CGDWebServer/GCDWebServerRequest.m

@@ -25,8 +25,116 @@
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#import <zlib.h>
+
 #import "GCDWebServerPrivate.h"
 
+#define kZlibErrorDomain @"ZlibErrorDomain"
+#define kGZipInitialBufferSize (256 * 1024)
+
+@interface GCDWebServerBodyDecoder : NSObject <GCDWebServerBodyWriter>
+- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer;
+@end
+
+@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder
+@end
+
+@interface GCDWebServerBodyDecoder () {
+@private
+  GCDWebServerRequest* __unsafe_unretained _request;
+  id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
+}
+@end
+
+@implementation GCDWebServerBodyDecoder
+
+- (id)initWithRequest:(GCDWebServerRequest*)request writer:(id<GCDWebServerBodyWriter>)writer {
+  if ((self = [super init])) {
+    _request = request;
+    _writer = writer;
+  }
+  return self;
+}
+
+- (BOOL)open:(NSError**)error {
+  return [_writer open:error];
+}
+
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
+  return [_writer writeData:data error:error];
+}
+
+- (BOOL)close:(NSError**)error {
+  return [_writer close:error];
+}
+
+@end
+
+@interface GCDWebServerGZipDecoder () {
+@private
+  z_stream _stream;
+  BOOL _finished;
+}
+@end
+
+@implementation GCDWebServerGZipDecoder
+
+- (BOOL)open:(NSError**)error {
+  int result = inflateInit2(&_stream, 15 + 16);
+  if (result != Z_OK) {
+    *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+    return NO;
+  }
+  if (![super open:error]) {
+    deflateEnd(&_stream);
+    return NO;
+  }
+  return YES;
+}
+
+- (BOOL)writeData:(NSData*)data error:(NSError**)error {
+  DCHECK(!_finished);
+  _stream.next_in = (Bytef*)data.bytes;
+  _stream.avail_in = (uInt)data.length;
+  NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:1024]; // kGZipInitialBufferSize
+  if (decodedData == nil) {
+    DNOT_REACHED();
+    return NO;
+  }
+  NSUInteger length = 0;
+  while (1) {
+    NSUInteger maxLength = decodedData.length - length;
+    _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length);
+    _stream.avail_out = (uInt)maxLength;
+    int result = inflate(&_stream, Z_NO_FLUSH);
+    if ((result != Z_OK) && (result != Z_STREAM_END)) {
+      ARC_RELEASE(decodedData);
+      *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+      return nil;
+    }
+    length += maxLength - _stream.avail_out;
+    if (_stream.avail_out > 0) {
+      if (result == Z_STREAM_END) {
+        _finished = YES;
+      }
+      break;
+    }
+    decodedData.length = 2 * decodedData.length;  // zlib has used all the output buffer so resize it and try again in case more data is available
+  }
+  decodedData.length = length;
+  BOOL success = length ? [super writeData:decodedData error:error] : YES;  // No need to call writer if we have no data yet
+  ARC_RELEASE(decodedData);
+  return success;
+}
+
+- (BOOL)close:(NSError**)error {
+  DCHECK(_finished);
+  inflateEnd(&_stream);
+  return [super close:error];
+}
+
+@end
+
 @interface GCDWebServerRequest () {
 @private
   NSString* _method;
@@ -144,7 +252,12 @@
   _opened = YES;
   
   _writer = self;
-  // TODO: Inject decoders
+  if ([[[self.headers objectForKey:@"Content-Encoding"] lowercaseString] isEqualToString:@"gzip"]) {
+    GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer];
+    [_decoders addObject:decoder];
+    ARC_RELEASE(decoder);
+    _writer = decoder;
+  }
   return [_writer open:error];
 }