ASIFormDataRequest.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. //
  2. // ASIFormDataRequest.m
  3. // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4. //
  5. // Created by Ben Copsey on 07/11/2008.
  6. // Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
  7. //
  8. #import "ASIFormDataRequest.h"
  9. // Private stuff
  10. @interface ASIFormDataRequest ()
  11. - (void)buildMultipartFormDataPostBody;
  12. - (void)buildURLEncodedPostBody;
  13. - (void)appendPostString:(NSString *)string;
  14. @property (atomic, retain) NSMutableArray *postData;
  15. @property (atomic, retain) NSMutableArray *fileData;
  16. #if DEBUG_FORM_DATA_REQUEST
  17. - (void)addToDebugBody:(NSString *)string;
  18. @property (retain, nonatomic) NSString *debugBodyString;
  19. #endif
  20. @end
  21. @implementation ASIFormDataRequest
  22. #pragma mark utilities
  23. - (NSString*)encodeURL:(NSString *)string
  24. {
  25. NSString *newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease];
  26. if (newString) {
  27. return newString;
  28. }
  29. return @"";
  30. }
  31. #pragma mark init / dealloc
  32. + (id)requestWithURL:(NSURL *)newURL
  33. {
  34. return [[[self alloc] initWithURL:newURL] autorelease];
  35. }
  36. - (id)initWithURL:(NSURL *)newURL
  37. {
  38. self = [super initWithURL:newURL];
  39. [self setPostFormat:ASIURLEncodedPostFormat];
  40. [self setStringEncoding:NSUTF8StringEncoding];
  41. [self setRequestMethod:@"POST"];
  42. return self;
  43. }
  44. - (void)dealloc
  45. {
  46. #if DEBUG_FORM_DATA_REQUEST
  47. [debugBodyString release];
  48. #endif
  49. [postData release];
  50. [fileData release];
  51. [super dealloc];
  52. }
  53. #pragma mark setup request
  54. - (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key
  55. {
  56. if (!key) {
  57. return;
  58. }
  59. if (![self postData]) {
  60. [self setPostData:[NSMutableArray array]];
  61. }
  62. NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2];
  63. [keyValuePair setValue:key forKey:@"key"];
  64. [keyValuePair setValue:[value description] forKey:@"value"];
  65. [[self postData] addObject:keyValuePair];
  66. }
  67. - (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key
  68. {
  69. // Remove any existing value
  70. NSUInteger i;
  71. for (i=0; i<[[self postData] count]; i++) {
  72. NSDictionary *val = [[self postData] objectAtIndex:i];
  73. if ([[val objectForKey:@"key"] isEqualToString:key]) {
  74. [[self postData] removeObjectAtIndex:i];
  75. i--;
  76. }
  77. }
  78. [self addPostValue:value forKey:key];
  79. }
  80. - (void)addFile:(NSString *)filePath forKey:(NSString *)key
  81. {
  82. [self addFile:filePath withFileName:nil andContentType:nil forKey:key];
  83. }
  84. - (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
  85. {
  86. BOOL isDirectory = NO;
  87. BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory];
  88. if (!fileExists || isDirectory) {
  89. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],NSLocalizedDescriptionKey,nil]]];
  90. }
  91. // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed
  92. if (!fileName) {
  93. fileName = [filePath lastPathComponent];
  94. }
  95. // 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
  96. if (!contentType) {
  97. contentType = [ASIHTTPRequest mimeTypeForFileAtPath:filePath];
  98. }
  99. [self addData:filePath withFileName:fileName andContentType:contentType forKey:key];
  100. }
  101. - (void)setFile:(NSString *)filePath forKey:(NSString *)key
  102. {
  103. [self setFile:filePath withFileName:nil andContentType:nil forKey:key];
  104. }
  105. - (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
  106. {
  107. // Remove any existing value
  108. NSUInteger i;
  109. for (i=0; i<[[self fileData] count]; i++) {
  110. NSDictionary *val = [[self fileData] objectAtIndex:i];
  111. if ([[val objectForKey:@"key"] isEqualToString:key]) {
  112. [[self fileData] removeObjectAtIndex:i];
  113. i--;
  114. }
  115. }
  116. [self addFile:data withFileName:fileName andContentType:contentType forKey:key];
  117. }
  118. - (void)addData:(NSData *)data forKey:(NSString *)key
  119. {
  120. [self addData:data withFileName:@"file" andContentType:nil forKey:key];
  121. }
  122. - (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
  123. {
  124. if (![self fileData]) {
  125. [self setFileData:[NSMutableArray array]];
  126. }
  127. if (!contentType) {
  128. contentType = @"application/octet-stream";
  129. }
  130. NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4];
  131. [fileInfo setValue:key forKey:@"key"];
  132. [fileInfo setValue:fileName forKey:@"fileName"];
  133. [fileInfo setValue:contentType forKey:@"contentType"];
  134. [fileInfo setValue:data forKey:@"data"];
  135. [[self fileData] addObject:fileInfo];
  136. }
  137. - (void)setData:(NSData *)data forKey:(NSString *)key
  138. {
  139. [self setData:data withFileName:@"file" andContentType:nil forKey:key];
  140. }
  141. - (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
  142. {
  143. // Remove any existing value
  144. NSUInteger i;
  145. for (i=0; i<[[self fileData] count]; i++) {
  146. NSDictionary *val = [[self fileData] objectAtIndex:i];
  147. if ([[val objectForKey:@"key"] isEqualToString:key]) {
  148. [[self fileData] removeObjectAtIndex:i];
  149. i--;
  150. }
  151. }
  152. [self addData:data withFileName:fileName andContentType:contentType forKey:key];
  153. }
  154. - (void)buildPostBody
  155. {
  156. if ([self haveBuiltPostBody]) {
  157. return;
  158. }
  159. #if DEBUG_FORM_DATA_REQUEST
  160. [self setDebugBodyString:@""];
  161. #endif
  162. if (![self postData] && ![self fileData]) {
  163. [super buildPostBody];
  164. return;
  165. }
  166. if ([[self fileData] count] > 0) {
  167. [self setShouldStreamPostDataFromDisk:YES];
  168. }
  169. if ([self postFormat] == ASIURLEncodedPostFormat) {
  170. [self buildURLEncodedPostBody];
  171. } else {
  172. [self buildMultipartFormDataPostBody];
  173. }
  174. [super buildPostBody];
  175. #if DEBUG_FORM_DATA_REQUEST
  176. ASI_DEBUG_LOG(@"%@",[self debugBodyString]);
  177. [self setDebugBodyString:nil];
  178. #endif
  179. }
  180. - (void)buildMultipartFormDataPostBody
  181. {
  182. #if DEBUG_FORM_DATA_REQUEST
  183. [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"];
  184. #endif
  185. NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
  186. // We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
  187. CFUUIDRef uuid = CFUUIDCreate(nil);
  188. NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease];
  189. CFRelease(uuid);
  190. NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString];
  191. [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
  192. [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]];
  193. // Adds post data
  194. NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary];
  195. NSUInteger i=0;
  196. for (NSDictionary *val in [self postData]) {
  197. [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]];
  198. [self appendPostString:[val objectForKey:@"value"]];
  199. i++;
  200. if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body
  201. [self appendPostString:endItemBoundary];
  202. }
  203. }
  204. // Adds files to upload
  205. i=0;
  206. for (NSDictionary *val in [self fileData]) {
  207. [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]];
  208. [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]];
  209. id data = [val objectForKey:@"data"];
  210. if ([data isKindOfClass:[NSString class]]) {
  211. [self appendPostDataFromFile:data];
  212. } else {
  213. [self appendPostData:data];
  214. }
  215. i++;
  216. // Only add the boundary if this is not the last item in the post body
  217. if (i != [[self fileData] count]) {
  218. [self appendPostString:endItemBoundary];
  219. }
  220. }
  221. [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]];
  222. #if DEBUG_FORM_DATA_REQUEST
  223. [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"];
  224. #endif
  225. }
  226. - (void)buildURLEncodedPostBody
  227. {
  228. // We can't post binary data using application/x-www-form-urlencoded
  229. if ([[self fileData] count] > 0) {
  230. [self setPostFormat:ASIMultipartFormDataPostFormat];
  231. [self buildMultipartFormDataPostBody];
  232. return;
  233. }
  234. #if DEBUG_FORM_DATA_REQUEST
  235. [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"];
  236. #endif
  237. NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
  238. [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
  239. NSUInteger i=0;
  240. NSUInteger count = [[self postData] count]-1;
  241. for (NSDictionary *val in [self postData]) {
  242. NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")];
  243. [self appendPostString:data];
  244. i++;
  245. }
  246. #if DEBUG_FORM_DATA_REQUEST
  247. [self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"];
  248. #endif
  249. }
  250. - (void)appendPostString:(NSString *)string
  251. {
  252. #if DEBUG_FORM_DATA_REQUEST
  253. [self addToDebugBody:string];
  254. #endif
  255. [super appendPostData:[string dataUsingEncoding:[self stringEncoding]]];
  256. }
  257. #if DEBUG_FORM_DATA_REQUEST
  258. - (void)appendPostData:(NSData *)data
  259. {
  260. [self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]];
  261. [super appendPostData:data];
  262. }
  263. - (void)appendPostDataFromFile:(NSString *)file
  264. {
  265. NSError *err = nil;
  266. unsigned long long fileSize = [[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue];
  267. if (err) {
  268. [self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]];
  269. } else {
  270. [self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]];
  271. }
  272. [super appendPostDataFromFile:file];
  273. }
  274. - (void)addToDebugBody:(NSString *)string
  275. {
  276. if (string) {
  277. [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]];
  278. }
  279. }
  280. #endif
  281. #pragma mark NSCopying
  282. - (id)copyWithZone:(NSZone *)zone
  283. {
  284. ASIFormDataRequest *newRequest = [super copyWithZone:zone];
  285. [newRequest setPostData:[[[self postData] mutableCopyWithZone:zone] autorelease]];
  286. [newRequest setFileData:[[[self fileData] mutableCopyWithZone:zone] autorelease]];
  287. [newRequest setPostFormat:[self postFormat]];
  288. [newRequest setStringEncoding:[self stringEncoding]];
  289. [newRequest setRequestMethod:[self requestMethod]];
  290. return newRequest;
  291. }
  292. @synthesize postData;
  293. @synthesize fileData;
  294. @synthesize postFormat;
  295. @synthesize stringEncoding;
  296. #if DEBUG_FORM_DATA_REQUEST
  297. @synthesize debugBodyString;
  298. #endif
  299. @end