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