root / Classes / ASIFormDataRequest.m @ 9fee07a6
History | View | Annotate | Download (10.8 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 (![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 |