//
//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
@interfaceASIFormDataRequest ()- (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
@implementationASIFormDataRequest#pragma mark utilities
- (NSString*)encodeURL:(NSString *)string{
NSString*newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease];if(newString) {returnnewString;
}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"];returnself;
}- (void)dealloc
{#if DEBUG_FORM_DATA_REQUEST[debugBodyString release];#endif[postData release];
[fileData release];
[super dealloc];
}#pragma mark setup request
- (void)addPostValue:(id )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 )value forKey:(NSString *)key
{//Remove any existing value
NSUInteger i;for (i=0; 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
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
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_REQUESTASI_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"];#endifNSString*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"];#endifNSString*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
[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:[stringdataUsingEncoding:[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;
unsignedlong 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]];returnnewRequest;
}@synthesizepostData;@synthesizefileData;@synthesizepostFormat;@synthesizestringEncoding;#if DEBUG_FORM_DATA_REQUEST
@synthesizedebugBodyString;#endif
@end