New file |
| | |
| | | // |
| | | // 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 |