#import "GSTransferState.h" #import "GSURLSessionTaskBodySource.h" #import "Foundation/NSArray.h" #import "Foundation/NSCharacterSet.h" #import "Foundation/NSData.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSFileHandle.h" #import "Foundation/NSError.h" #import "Foundation/NSURL.h" #import "Foundation/NSURLError.h" #import "Foundation/NSURLResponse.h" #import "Foundation/NSURLSession.h" #import "Foundation/NSUUID.h" #import "Foundation/NSFileManager.h" #import "Foundation/NSPathUtilities.h" #define GS_DELIMITERS_CR 0x0d #define GS_DELIMITERS_LR 0x0a @implementation GSParsedResponseHeader - (instancetype) init { if (nil != (self = [super init])) { _lines = [[NSMutableArray alloc] init]; _type = GSParsedResponseHeaderTypePartial; } return self; } - (void) dealloc { DESTROY(_lines); [super dealloc]; } - (GSParsedResponseHeaderType) type { return _type; } - (void) setType: (GSParsedResponseHeaderType)type { _type = type; } - (void) setLines: (NSArray*)lines { ASSIGN(_lines, lines); } - (instancetype) byAppendingHeaderLine: (NSData*)data { NSUInteger length = [data length]; if (length >= 2) { uint8_t last2; uint8_t last1; [data getBytes: &last2 range: NSMakeRange(length - 2, 1)]; [data getBytes: &last1 range: NSMakeRange(length - 1, 1)]; if (GS_DELIMITERS_CR == last2 && GS_DELIMITERS_LR == last1) { NSData *lineBuffer; NSString *line; lineBuffer = [data subdataWithRange: NSMakeRange(0, length - 2)]; line = AUTORELEASE([[NSString alloc] initWithData: lineBuffer encoding: NSUTF8StringEncoding]); if (nil == line) { return nil; } return [self _byAppendingHeaderLine: line]; } } return nil; } - (NSHTTPURLResponse*) createHTTPURLResponseForURL: (NSURL*)URL { NSArray *tail; NSArray *startLine; NSDictionary *headerFields; NSString *head; NSString *s, *v; head = [_lines firstObject]; if (nil == head) { return nil; } if ([_lines count] == 0) { return nil; } tail = [_lines subarrayWithRange: NSMakeRange(1, [_lines count] - 1)]; startLine = [self statusLineFromLine: head]; if (nil == startLine) { return nil; } headerFields = [self createHeaderFieldsFromLines: tail]; v = [startLine objectAtIndex: 0]; s = [startLine objectAtIndex: 1]; return AUTORELEASE([[NSHTTPURLResponse alloc] initWithURL: URL statusCode: [s integerValue] HTTPVersion: v headerFields: headerFields]); } - (NSArray*) statusLineFromLine: (NSString*)line { NSArray *a; NSString *s; NSInteger status; a = [line componentsSeparatedByString: @" "]; if ([a count] < 3) { return nil; } s = [a objectAtIndex: 1]; status = [s integerValue]; if (status >= 100 && status <= 999) { return a; } else { return nil; } } - (NSDictionary *) createHeaderFieldsFromLines: (NSArray *)lines { NSMutableDictionary *headerFields = nil; NSEnumerator *e; NSString *line; e = [_lines objectEnumerator]; while (nil != (line = [e nextObject])) { NSRange r; NSString *head; NSString *tail; NSCharacterSet *set; NSString *key; NSString *value; NSString *v; r = [line rangeOfString: @":"]; if (r.location != NSNotFound) { head = [line substringToIndex: r.location]; tail = [line substringFromIndex: r.location + 1]; set = [NSCharacterSet whitespaceAndNewlineCharacterSet]; key = [head stringByTrimmingCharactersInSet: set]; value = [tail stringByTrimmingCharactersInSet: set]; if (nil != key && nil != value) { if (nil == headerFields) { headerFields = [NSMutableDictionary dictionary]; } if (nil != [headerFields objectForKey: key]) { v = [NSString stringWithFormat:@"%@, %@", [headerFields objectForKey: key], value]; [headerFields setObject: v forKey: key]; } else { [headerFields setObject: value forKey: key]; } } } else { continue; } } return AUTORELEASE([headerFields copy]); } - (instancetype) _byAppendingHeaderLine: (NSString*)line { GSParsedResponseHeader *header; header = AUTORELEASE([[GSParsedResponseHeader alloc] init]); if ([line length] == 0) { switch (_type) { case GSParsedResponseHeaderTypePartial: { [header setType: GSParsedResponseHeaderTypeComplete]; [header setLines: _lines]; return header; } case GSParsedResponseHeaderTypeComplete: return header; } } else { NSMutableArray *lines = [[self partialResponseHeader] mutableCopy]; [lines addObject:line]; [header setType: GSParsedResponseHeaderTypePartial]; [header setLines: lines]; RELEASE(lines); return header; } } - (NSArray*) partialResponseHeader { switch (_type) { case GSParsedResponseHeaderTypeComplete: return [NSArray array]; case GSParsedResponseHeaderTypePartial: return _lines; } } @end @implementation GSDataDrain - (void) dealloc { DESTROY(_data); DESTROY(_fileURL); DESTROY(_fileHandle); [super dealloc]; } - (GSDataDrainType) type { return _type; } - (void) setType: (GSDataDrainType)type { _type = type; } - (NSMutableData*) data { if (!_data) { _data = [[NSMutableData alloc] init]; } return _data; } - (NSURL*) fileURL { /* Generate a random fileURL if fileURL is not initialized. * It would be nice to have this implemented in an initializer. */ if (!_fileURL) { NSUUID *randomUUID; NSString *fileName; NSURL *tempURL; randomUUID = [NSUUID UUID]; fileName = [[randomUUID UUIDString] stringByAppendingPathExtension: @"tmp"]; tempURL = [NSURL fileURLWithPath: NSTemporaryDirectory()]; _fileURL = RETAIN([NSURL fileURLWithPath: fileName relativeToURL: tempURL]); } return _fileURL; } - (NSFileHandle*) fileHandle { /* Create temporary file and open a fileHandle for writing. */ if (!_fileHandle) { NSFileManager *fileManager = [NSFileManager defaultManager]; [fileManager createFileAtPath: [[self fileURL] relativePath] contents: nil attributes: nil]; _fileHandle = RETAIN([NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL]); } return _fileHandle; } @end @implementation GSTransferState - (instancetype) initWithURL: (NSURL*)url bodyDataDrain: (GSDataDrain*)bodyDataDrain { return [self initWithURL: url parsedResponseHeader: nil response: nil bodySource: nil bodyDataDrain: bodyDataDrain]; } - (instancetype) initWithURL: (NSURL*)url bodyDataDrain: (GSDataDrain*)bodyDataDrain bodySource: (id)bodySource { return [self initWithURL: url parsedResponseHeader: nil response: nil bodySource: bodySource bodyDataDrain: bodyDataDrain]; } - (instancetype) initWithURL: (NSURL*)url parsedResponseHeader: (GSParsedResponseHeader*)parsedResponseHeader response: (NSURLResponse*)response bodySource: (id)bodySource bodyDataDrain: (GSDataDrain*)bodyDataDrain { if (nil != (self = [super init])) { ASSIGN(_url, url); if (nil != parsedResponseHeader) { ASSIGN(_parsedResponseHeader, parsedResponseHeader); } else { _parsedResponseHeader = [[GSParsedResponseHeader alloc] init]; } ASSIGN(_response, response); ASSIGN(_requestBodySource, bodySource); ASSIGN(_bodyDataDrain, bodyDataDrain); } return self; } - (void) dealloc { DESTROY(_url); DESTROY(_parsedResponseHeader); DESTROY(_response); DESTROY(_requestBodySource); DESTROY(_bodyDataDrain); [super dealloc]; } - (instancetype) byAppendingBodyData: (NSData*)bodyData { switch ([_bodyDataDrain type]) { case GSDataDrainInMemory: [[_bodyDataDrain data] appendData: bodyData]; return self; case GSDataDrainTypeToFile: { NSFileHandle *fileHandle; fileHandle = [_bodyDataDrain fileHandle]; [fileHandle seekToEndOfFile]; [fileHandle writeData: bodyData]; return self; } case GSDataDrainTypeIgnore: return self; } } - (instancetype) byAppendingHTTPHeaderLineData: (NSData*)data error: (NSError**)error { GSParsedResponseHeader *h; h = [_parsedResponseHeader byAppendingHeaderLine: data]; if (nil == h) { if (error != NULL) { *error = [NSError errorWithDomain: NSURLErrorDomain code: -1 userInfo: nil]; } return nil; } if ([h type] == GSParsedResponseHeaderTypeComplete) { NSHTTPURLResponse *response; GSParsedResponseHeader *ph; GSTransferState *ts; response = [h createHTTPURLResponseForURL: _url]; if (nil == response) { if (error != NULL) { *error = [NSError errorWithDomain: NSURLErrorDomain code: -1 userInfo: nil]; } return nil; } ph = AUTORELEASE([[GSParsedResponseHeader alloc] init]); ts = [[GSTransferState alloc] initWithURL: _url parsedResponseHeader: ph response: response bodySource: _requestBodySource bodyDataDrain: _bodyDataDrain]; return AUTORELEASE(ts); } else { GSTransferState *ts; ts = [[GSTransferState alloc] initWithURL: _url parsedResponseHeader: h response: nil bodySource: _requestBodySource bodyDataDrain: _bodyDataDrain]; return AUTORELEASE(ts); } } - (BOOL) isHeaderComplete { return _response != nil; } - (NSURLResponse*) response { return _response; } - (void) setResponse: (NSURLResponse*)response { ASSIGN(_response, response); } - (id) requestBodySource { return _requestBodySource; } - (GSDataDrain*) bodyDataDrain { return _bodyDataDrain; } - (NSURL*) URL { return _url; } @end