From f7d6da55cd170ad76ae0dc52ec607e45d8217f52 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Latour Date: Thu, 24 Apr 2014 18:45:34 -0700 Subject: [PATCH] Added GCDWebServerMIMEStreamParser class --- .../GCDWebServerMultiPartFormRequest.m | 193 +++++++++++------- 1 file changed, 117 insertions(+), 76 deletions(-) diff --git a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m index db96d20..3dd8224 100644 --- a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m +++ b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m @@ -29,13 +29,19 @@ #define kMultiPartBufferSize (256 * 1024) -enum { +typedef enum { kParserState_Undefined = 0, kParserState_Start, kParserState_Headers, kParserState_Content, kParserState_End -}; +} ParserState; + +@interface GCDWebServerMIMEStreamParser : NSObject +- (instancetype)initWithBoundary:(NSString*)boundary arguments:(NSMutableDictionary*)arguments files:(NSMutableDictionary*)files; +- (BOOL)appendData:(NSData*)data; +- (BOOL)isAtEnd; +@end static NSData* _newlineData = nil; static NSData* _newlinesData = nil; @@ -139,26 +145,23 @@ static NSData* _dashNewlineData = nil; @end -@interface GCDWebServerMultiPartFormRequest () { +@interface GCDWebServerMIMEStreamParser () { @private NSData* _boundary; + ParserState _state; + NSMutableData* _data; + NSMutableDictionary* _arguments; + NSMutableDictionary* _files; - NSUInteger _parserState; - NSMutableData* _parserData; NSString* _controlName; NSString* _fileName; NSString* _contentType; NSString* _tmpPath; int _tmpFile; - - NSMutableDictionary* _arguments; - NSMutableDictionary* _files; } @end -@implementation GCDWebServerMultiPartFormRequest - -@synthesize arguments=_arguments, files=_files; +@implementation GCDWebServerMIMEStreamParser + (void)initialize { if (_newlineData == nil) { @@ -175,49 +178,47 @@ static NSData* _dashNewlineData = nil; } } -+ (NSString*)mimeType { - return @"multipart/form-data"; -} - -- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { - NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); - if (boundary) { - NSData* data = [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding]; - _boundary = ARC_RETAIN(data); - } - if (_boundary == nil) { - DNOT_REACHED(); - ARC_RELEASE(self); - return nil; - } - - _arguments = [[NSMutableDictionary alloc] init]; - _files = [[NSMutableDictionary alloc] init]; +- (instancetype)initWithBoundary:(NSString*)boundary arguments:(NSMutableDictionary*)arguments files:(NSMutableDictionary*)files { + NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; + if (data == nil) { + DNOT_REACHED(); + ARC_RELEASE(self); + return nil; + } + if ((self = [super init])) { + _boundary = ARC_RETAIN(data); + _data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; + _arguments = ARC_RETAIN(arguments); + _files = ARC_RETAIN(files); + _state = kParserState_Start; } return self; } - (void)dealloc { + ARC_RELEASE(_boundary); + ARC_RELEASE(_data); ARC_RELEASE(_arguments); ARC_RELEASE(_files); - ARC_RELEASE(_boundary); + + ARC_RELEASE(_controlName); + ARC_RELEASE(_fileName); + ARC_RELEASE(_contentType); + if (_tmpFile > 0) { + close(_tmpFile); + unlink([_tmpPath fileSystemRepresentation]); + } + ARC_RELEASE(_tmpPath); ARC_DEALLOC(super); } -- (BOOL)open:(NSError**)error { - _parserData = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; - _parserState = kParserState_Start; - return YES; -} - // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - (BOOL)_parseData { BOOL success = YES; - if (_parserState == kParserState_Headers) { - NSRange range = [_parserData rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _parserData.length)]; + if (_state == kParserState_Headers) { + NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; if (range.location != NSNotFound) { ARC_RELEASE(_controlName); @@ -228,7 +229,7 @@ static NSData* _dashNewlineData = nil; _contentType = nil; ARC_RELEASE(_tmpPath); _tmpPath = nil; - NSString* headers = [[NSString alloc] initWithData:[_parserData subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; + NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; if (headers) { for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) { NSRange subRange = [header rangeOfString:@":"]; @@ -272,21 +273,21 @@ static NSData* _dashNewlineData = nil; success = NO; } - [_parserData replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; - _parserState = kParserState_Content; + [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; + _state = kParserState_Content; } } - if ((_parserState == kParserState_Start) || (_parserState == kParserState_Content)) { - NSRange range = [_parserData rangeOfData:_boundary options:0 range:NSMakeRange(0, _parserData.length)]; + if ((_state == kParserState_Start) || (_state == kParserState_Content)) { + NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; if (range.location != NSNotFound) { - NSRange subRange = NSMakeRange(range.location + range.length, _parserData.length - range.location - range.length); - NSRange subRange1 = [_parserData rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; - NSRange subRange2 = [_parserData rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; + NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length); + NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; + NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { - if (_parserState == kParserState_Content) { - const void* dataBytes = _parserData.bytes; + if (_state == kParserState_Content) { + const void* dataBytes = _data.bytes; NSUInteger dataLength = range.location - 2; if (_tmpPath) { ssize_t result = write(_tmpFile, dataBytes, dataLength); @@ -316,20 +317,20 @@ static NSData* _dashNewlineData = nil; } if (subRange1.location != NSNotFound) { - [_parserData replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; - _parserState = kParserState_Headers; + [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; + _state = kParserState_Headers; success = [self _parseData]; } else { - _parserState = kParserState_End; + _state = kParserState_End; } } } else { NSUInteger margin = 2 * _boundary.length; - if (_tmpPath && (_parserData.length > margin)) { - NSUInteger length = _parserData.length - margin; - ssize_t result = write(_tmpFile, _parserData.bytes, length); + if (_tmpPath && (_data.length > margin)) { + NSUInteger length = _data.length - margin; + ssize_t result = write(_tmpFile, _data.bytes, length); if (result == (ssize_t)length) { - [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; + [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; } else { DNOT_REACHED(); success = NO; @@ -337,36 +338,76 @@ static NSData* _dashNewlineData = nil; } } } + return success; } +- (BOOL)appendData:(NSData*)data { + [_data appendBytes:data.bytes length:data.length]; + return [self _parseData]; +} + +- (BOOL)isAtEnd { + return (_state == kParserState_End); +} + +@end + +@interface GCDWebServerMultiPartFormRequest () { +@private + GCDWebServerMIMEStreamParser* _parser; + NSMutableDictionary* _arguments; + NSMutableDictionary* _files; +} +@end + +@implementation GCDWebServerMultiPartFormRequest + +@synthesize arguments=_arguments, files=_files; + ++ (NSString*)mimeType { + return @"multipart/form-data"; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { + _arguments = [[NSMutableDictionary alloc] init]; + _files = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dealloc { + ARC_RELEASE(_arguments); + ARC_RELEASE(_files); + + ARC_DEALLOC(super); +} + +- (BOOL)open:(NSError**)error { + NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); + _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary arguments:_arguments files:_files]; + if (_parser == nil) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}]; + return NO; + } + return YES; +} + - (BOOL)writeData:(NSData*)data error:(NSError**)error { - [_parserData appendBytes:data.bytes length:data.length]; - if (![self _parseData]) { - *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}]; + if (![_parser appendData:data]) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}]; return NO; } return YES; } - (BOOL)close:(NSError**)error { - ARC_RELEASE(_parserData); - _parserData = nil; - ARC_RELEASE(_controlName); - _controlName = nil; - ARC_RELEASE(_fileName); - _fileName = nil; - ARC_RELEASE(_contentType); - _contentType = nil; - if (_tmpFile > 0) { - close(_tmpFile); - unlink([_tmpPath fileSystemRepresentation]); - _tmpFile = 0; - } - ARC_RELEASE(_tmpPath); - _tmpPath = nil; - if (_parserState != kParserState_End) { - *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed parsing multipart form data"}]; + BOOL atEnd = [_parser isAtEnd]; + ARC_RELEASE(_parser); + _parser = nil; + if (!atEnd) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}]; return NO; } return YES;