root / asi-http-request-with-pithos / Classes / ASIFormDataRequest.m @ be116d22
History | View | Annotate | Download (11 kB)
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 (!key) { |
71 |
return; |
72 |
} |
73 |
if (![self postData]) { |
74 |
[self setPostData:[NSMutableArray array]]; |
75 |
} |
76 |
NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2]; |
77 |
[keyValuePair setValue:key forKey:@"key"]; |
78 |
[keyValuePair setValue:[value description] forKey:@"value"]; |
79 |
[[self postData] addObject:keyValuePair]; |
80 |
} |
81 |
|
82 |
- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key |
83 |
{ |
84 |
// Remove any existing value |
85 |
NSUInteger i; |
86 |
for (i=0; i<[[self postData] count]; i++) { |
87 |
NSDictionary *val = [[self postData] objectAtIndex:i]; |
88 |
if ([[val objectForKey:@"key"] isEqualToString:key]) { |
89 |
[[self postData] removeObjectAtIndex:i]; |
90 |
i--; |
91 |
} |
92 |
} |
93 |
[self addPostValue:value forKey:key]; |
94 |
} |
95 |
|
96 |
|
97 |
- (void)addFile:(NSString *)filePath forKey:(NSString *)key |
98 |
{ |
99 |
[self addFile:filePath withFileName:nil andContentType:nil forKey:key]; |
100 |
} |
101 |
|
102 |
- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key |
103 |
{ |
104 |
BOOL isDirectory = NO; |
105 |
BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory]; |
106 |
if (!fileExists || isDirectory) { |
107 |
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],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 = [filePath 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:filePath]; |
118 |
} |
119 |
[self addData:filePath withFileName:fileName andContentType:contentType forKey:key]; |
120 |
} |
121 |
|
122 |
- (void)setFile:(NSString *)filePath forKey:(NSString *)key |
123 |
{ |
124 |
[self setFile:filePath withFileName:nil andContentType:nil forKey:key]; |
125 |
} |
126 |
|
127 |
- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key |
128 |
{ |
129 |
// Remove any existing value |
130 |
NSUInteger i; |
131 |
for (i=0; i<[[self fileData] count]; i++) { |
132 |
NSDictionary *val = [[self fileData] objectAtIndex:i]; |
133 |
if ([[val objectForKey:@"key"] isEqualToString:key]) { |
134 |
[[self fileData] removeObjectAtIndex:i]; |
135 |
i--; |
136 |
} |
137 |
} |
138 |
[self addFile:data withFileName:fileName andContentType:contentType forKey:key]; |
139 |
} |
140 |
|
141 |
- (void)addData:(NSData *)data forKey:(NSString *)key |
142 |
{ |
143 |
[self addData:data withFileName:@"file" andContentType:nil forKey:key]; |
144 |
} |
145 |
|
146 |
- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key |
147 |
{ |
148 |
if (![self fileData]) { |
149 |
[self setFileData:[NSMutableArray array]]; |
150 |
} |
151 |
if (!contentType) { |
152 |
contentType = @"application/octet-stream"; |
153 |
} |
154 |
|
155 |
NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4]; |
156 |
[fileInfo setValue:key forKey:@"key"]; |
157 |
[fileInfo setValue:fileName forKey:@"fileName"]; |
158 |
[fileInfo setValue:contentType forKey:@"contentType"]; |
159 |
[fileInfo setValue:data forKey:@"data"]; |
160 |
|
161 |
[[self fileData] addObject:fileInfo]; |
162 |
} |
163 |
|
164 |
- (void)setData:(NSData *)data forKey:(NSString *)key |
165 |
{ |
166 |
[self setData:data withFileName:@"file" andContentType:nil forKey:key]; |
167 |
} |
168 |
|
169 |
- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key |
170 |
{ |
171 |
// Remove any existing value |
172 |
NSUInteger i; |
173 |
for (i=0; i<[[self fileData] count]; i++) { |
174 |
NSDictionary *val = [[self fileData] objectAtIndex:i]; |
175 |
if ([[val objectForKey:@"key"] isEqualToString:key]) { |
176 |
[[self fileData] removeObjectAtIndex:i]; |
177 |
i--; |
178 |
} |
179 |
} |
180 |
[self addData:data withFileName:fileName andContentType:contentType forKey:key]; |
181 |
} |
182 |
|
183 |
- (void)buildPostBody |
184 |
{ |
185 |
if ([self haveBuiltPostBody]) { |
186 |
return; |
187 |
} |
188 |
|
189 |
#if DEBUG_FORM_DATA_REQUEST |
190 |
[self setDebugBodyString:@""]; |
191 |
#endif |
192 |
|
193 |
if (![self postData] && ![self fileData]) { |
194 |
[super buildPostBody]; |
195 |
return; |
196 |
} |
197 |
if ([[self fileData] count] > 0) { |
198 |
[self setShouldStreamPostDataFromDisk:YES]; |
199 |
} |
200 |
|
201 |
if ([self postFormat] == ASIURLEncodedPostFormat) { |
202 |
[self buildURLEncodedPostBody]; |
203 |
} else { |
204 |
[self buildMultipartFormDataPostBody]; |
205 |
} |
206 |
|
207 |
[super buildPostBody]; |
208 |
|
209 |
#if DEBUG_FORM_DATA_REQUEST |
210 |
NSLog(@"%@",[self debugBodyString]); |
211 |
[self setDebugBodyString:nil]; |
212 |
#endif |
213 |
} |
214 |
|
215 |
|
216 |
- (void)buildMultipartFormDataPostBody |
217 |
{ |
218 |
#if DEBUG_FORM_DATA_REQUEST |
219 |
[self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"]; |
220 |
#endif |
221 |
|
222 |
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); |
223 |
|
224 |
// We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. |
225 |
CFUUIDRef uuid = CFUUIDCreate(nil); |
226 |
NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease]; |
227 |
CFRelease(uuid); |
228 |
NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString]; |
229 |
|
230 |
[self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; |
231 |
|
232 |
[self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]]; |
233 |
|
234 |
// Adds post data |
235 |
NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; |
236 |
NSUInteger i=0; |
237 |
for (NSDictionary *val in [self postData]) { |
238 |
[self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]]; |
239 |
[self appendPostString:[val objectForKey:@"value"]]; |
240 |
i++; |
241 |
if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body |
242 |
[self appendPostString:endItemBoundary]; |
243 |
} |
244 |
} |
245 |
|
246 |
// Adds files to upload |
247 |
i=0; |
248 |
for (NSDictionary *val in [self fileData]) { |
249 |
|
250 |
[self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]]; |
251 |
[self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]]; |
252 |
|
253 |
id data = [val objectForKey:@"data"]; |
254 |
if ([data isKindOfClass:[NSString class]]) { |
255 |
[self appendPostDataFromFile:data]; |
256 |
} else { |
257 |
[self appendPostData:data]; |
258 |
} |
259 |
i++; |
260 |
// Only add the boundary if this is not the last item in the post body |
261 |
if (i != [[self fileData] count]) { |
262 |
[self appendPostString:endItemBoundary]; |
263 |
} |
264 |
} |
265 |
|
266 |
[self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]]; |
267 |
|
268 |
#if DEBUG_FORM_DATA_REQUEST |
269 |
[self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"]; |
270 |
#endif |
271 |
} |
272 |
|
273 |
- (void)buildURLEncodedPostBody |
274 |
{ |
275 |
|
276 |
// We can't post binary data using application/x-www-form-urlencoded |
277 |
if ([[self fileData] count] > 0) { |
278 |
[self setPostFormat:ASIMultipartFormDataPostFormat]; |
279 |
[self buildMultipartFormDataPostBody]; |
280 |
return; |
281 |
} |
282 |
|
283 |
#if DEBUG_FORM_DATA_REQUEST |
284 |
[self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"]; |
285 |
#endif |
286 |
|
287 |
|
288 |
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); |
289 |
|
290 |
[self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; |
291 |
|
292 |
|
293 |
NSUInteger i=0; |
294 |
NSUInteger count = [[self postData] count]-1; |
295 |
for (NSDictionary *val in [self postData]) { |
296 |
NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")]; |
297 |
[self appendPostString:data]; |
298 |
i++; |
299 |
} |
300 |
#if DEBUG_FORM_DATA_REQUEST |
301 |
[self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"]; |
302 |
#endif |
303 |
} |
304 |
|
305 |
- (void)appendPostString:(NSString *)string |
306 |
{ |
307 |
#if DEBUG_FORM_DATA_REQUEST |
308 |
[self addToDebugBody:string]; |
309 |
#endif |
310 |
[super appendPostData:[string dataUsingEncoding:[self stringEncoding]]]; |
311 |
} |
312 |
|
313 |
#if DEBUG_FORM_DATA_REQUEST |
314 |
- (void)appendPostData:(NSData *)data |
315 |
{ |
316 |
[self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]]; |
317 |
[super appendPostData:data]; |
318 |
} |
319 |
|
320 |
- (void)appendPostDataFromFile:(NSString *)file |
321 |
{ |
322 |
NSError *err = nil; |
323 |
unsigned long long fileSize = [[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue]; |
324 |
if (err) { |
325 |
[self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]]; |
326 |
} else { |
327 |
[self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]]; |
328 |
} |
329 |
|
330 |
[super appendPostDataFromFile:file]; |
331 |
} |
332 |
|
333 |
- (void)addToDebugBody:(NSString *)string |
334 |
{ |
335 |
if (string) { |
336 |
[self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]]; |
337 |
} |
338 |
} |
339 |
#endif |
340 |
|
341 |
#pragma mark NSCopying |
342 |
|
343 |
- (id)copyWithZone:(NSZone *)zone |
344 |
{ |
345 |
ASIFormDataRequest *newRequest = [super copyWithZone:zone]; |
346 |
[newRequest setPostData:[[[self postData] mutableCopyWithZone:zone] autorelease]]; |
347 |
[newRequest setFileData:[[[self fileData] mutableCopyWithZone:zone] autorelease]]; |
348 |
[newRequest setPostFormat:[self postFormat]]; |
349 |
[newRequest setStringEncoding:[self stringEncoding]]; |
350 |
[newRequest setRequestMethod:[self requestMethod]]; |
351 |
return newRequest; |
352 |
} |
353 |
|
354 |
@synthesize postData; |
355 |
@synthesize fileData; |
356 |
@synthesize postFormat; |
357 |
@synthesize stringEncoding; |
358 |
#if DEBUG_FORM_DATA_REQUEST |
359 |
@synthesize debugBodyString; |
360 |
#endif |
361 |
@end |