Statistics
| Branch: | Revision:

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