Statistics
| Branch: | Tag: | Revision:

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