Statistics
| Branch: | Revision:

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