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