// // ASIFormDataRequest.m // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest // // Created by Ben Copsey on 07/11/2008. // Copyright 2008-2009 All-Seeing Interactive. All rights reserved. // #import "ASIFormDataRequest.h" // Private stuff @interface ASIFormDataRequest () - (void)buildMultipartFormDataPostBody; - (void)buildURLEncodedPostBody; - (void)appendPostString:(NSString *)string; @property (retain) NSMutableArray *postData; @property (retain) NSMutableArray *fileData; #if DEBUG_FORM_DATA_REQUEST - (void)addToDebugBody:(NSString *)string; @property (retain, nonatomic) NSString *debugBodyString; #endif @end @implementation ASIFormDataRequest #pragma mark utilities - (NSString*)encodeURL:(NSString *)string { NSString *newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease]; if (newString) { return newString; } return @""; } #pragma mark init / dealloc + (id)requestWithURL:(NSURL *)newURL { return [[[self alloc] initWithURL:newURL] autorelease]; } - (id)initWithURL:(NSURL *)newURL { self = [super initWithURL:newURL]; [self setPostFormat:ASIURLEncodedPostFormat]; [self setStringEncoding:NSUTF8StringEncoding]; [self setRequestMethod:@"POST"]; return self; } - (void)dealloc { #if DEBUG_FORM_DATA_REQUEST [debugBodyString release]; #endif [postData release]; [fileData release]; [super dealloc]; } #pragma mark setup request - (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key { if (!key) { return; } if (![self postData]) { [self setPostData:[NSMutableArray array]]; } NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2]; [keyValuePair setValue:key forKey:@"key"]; [keyValuePair setValue:[value description] forKey:@"value"]; [[self postData] addObject:keyValuePair]; } - (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key { // Remove any existing value NSUInteger i; for (i=0; i<[[self postData] count]; i++) { NSDictionary *val = [[self postData] objectAtIndex:i]; if ([[val objectForKey:@"key"] isEqualToString:key]) { [[self postData] removeObjectAtIndex:i]; i--; } } [self addPostValue:value forKey:key]; } - (void)addFile:(NSString *)filePath forKey:(NSString *)key { [self addFile:filePath withFileName:nil andContentType:nil forKey:key]; } - (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { BOOL isDirectory = NO; BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory]; if (!fileExists || isDirectory) { [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],NSLocalizedDescriptionKey,nil]]]; } // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed if (!fileName) { fileName = [filePath lastPathComponent]; } // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension if (!contentType) { contentType = [ASIHTTPRequest mimeTypeForFileAtPath:filePath]; } [self addData:filePath withFileName:fileName andContentType:contentType forKey:key]; } - (void)setFile:(NSString *)filePath forKey:(NSString *)key { [self setFile:filePath withFileName:nil andContentType:nil forKey:key]; } - (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { // Remove any existing value NSUInteger i; for (i=0; i<[[self fileData] count]; i++) { NSDictionary *val = [[self fileData] objectAtIndex:i]; if ([[val objectForKey:@"key"] isEqualToString:key]) { [[self fileData] removeObjectAtIndex:i]; i--; } } [self addFile:data withFileName:fileName andContentType:contentType forKey:key]; } - (void)addData:(NSData *)data forKey:(NSString *)key { [self addData:data withFileName:@"file" andContentType:nil forKey:key]; } - (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { if (![self fileData]) { [self setFileData:[NSMutableArray array]]; } if (!contentType) { contentType = @"application/octet-stream"; } NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4]; [fileInfo setValue:key forKey:@"key"]; [fileInfo setValue:fileName forKey:@"fileName"]; [fileInfo setValue:contentType forKey:@"contentType"]; [fileInfo setValue:data forKey:@"data"]; [[self fileData] addObject:fileInfo]; } - (void)setData:(NSData *)data forKey:(NSString *)key { [self setData:data withFileName:@"file" andContentType:nil forKey:key]; } - (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { // Remove any existing value NSUInteger i; for (i=0; i<[[self fileData] count]; i++) { NSDictionary *val = [[self fileData] objectAtIndex:i]; if ([[val objectForKey:@"key"] isEqualToString:key]) { [[self fileData] removeObjectAtIndex:i]; i--; } } [self addData:data withFileName:fileName andContentType:contentType forKey:key]; } - (void)buildPostBody { if ([self haveBuiltPostBody]) { return; } #if DEBUG_FORM_DATA_REQUEST [self setDebugBodyString:@""]; #endif if (![self postData] && ![self fileData]) { [super buildPostBody]; return; } if ([[self fileData] count] > 0) { [self setShouldStreamPostDataFromDisk:YES]; } if ([self postFormat] == ASIURLEncodedPostFormat) { [self buildURLEncodedPostBody]; } else { [self buildMultipartFormDataPostBody]; } [super buildPostBody]; #if DEBUG_FORM_DATA_REQUEST ASI_DEBUG_LOG(@"%@",[self debugBodyString]); [self setDebugBodyString:nil]; #endif } - (void)buildMultipartFormDataPostBody { #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"]; #endif NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); // We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. CFUUIDRef uuid = CFUUIDCreate(nil); NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease]; CFRelease(uuid); NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString]; [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]]; // Adds post data NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; NSUInteger i=0; for (NSDictionary *val in [self postData]) { [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]]; [self appendPostString:[val objectForKey:@"value"]]; i++; if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body [self appendPostString:endItemBoundary]; } } // Adds files to upload i=0; for (NSDictionary *val in [self fileData]) { [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]]; [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]]; id data = [val objectForKey:@"data"]; if ([data isKindOfClass:[NSString class]]) { [self appendPostDataFromFile:data]; } else { [self appendPostData:data]; } i++; // Only add the boundary if this is not the last item in the post body if (i != [[self fileData] count]) { [self appendPostString:endItemBoundary]; } } [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]]; #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"]; #endif } - (void)buildURLEncodedPostBody { // We can't post binary data using application/x-www-form-urlencoded if ([[self fileData] count] > 0) { [self setPostFormat:ASIMultipartFormDataPostFormat]; [self buildMultipartFormDataPostBody]; return; } #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"]; #endif NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; NSUInteger i=0; NSUInteger count = [[self postData] count]-1; for (NSDictionary *val in [self postData]) { NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")]; [self appendPostString:data]; i++; } #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"]; #endif } - (void)appendPostString:(NSString *)string { #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:string]; #endif [super appendPostData:[string dataUsingEncoding:[self stringEncoding]]]; } #if DEBUG_FORM_DATA_REQUEST - (void)appendPostData:(NSData *)data { [self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]]; [super appendPostData:data]; } - (void)appendPostDataFromFile:(NSString *)file { NSError *err = nil; unsigned long long fileSize = [[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue]; if (err) { [self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]]; } else { [self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]]; } [super appendPostDataFromFile:file]; } - (void)addToDebugBody:(NSString *)string { if (string) { [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]]; } } #endif #pragma mark NSCopying - (id)copyWithZone:(NSZone *)zone { ASIFormDataRequest *newRequest = [super copyWithZone:zone]; [newRequest setPostData:[[[self postData] mutableCopyWithZone:zone] autorelease]]; [newRequest setFileData:[[[self fileData] mutableCopyWithZone:zone] autorelease]]; [newRequest setPostFormat:[self postFormat]]; [newRequest setStringEncoding:[self stringEncoding]]; [newRequest setRequestMethod:[self requestMethod]]; return newRequest; } @synthesize postData; @synthesize fileData; @synthesize postFormat; @synthesize stringEncoding; #if DEBUG_FORM_DATA_REQUEST @synthesize debugBodyString; #endif @end