123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- //
- // 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 (atomic, retain) NSMutableArray *postData;
- @property (atomic, 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
|