Ben Copsey

Add new methods to allow ASIFormDataRequests to send multiple values for the same key

@@ -19,10 +19,10 @@ typedef enum _ASIPostFormat { @@ -19,10 +19,10 @@ typedef enum _ASIPostFormat {
19 @interface ASIFormDataRequest : ASIHTTPRequest <NSCopying> { 19 @interface ASIFormDataRequest : ASIHTTPRequest <NSCopying> {
20 20
21 // Parameters that will be POSTed to the url 21 // Parameters that will be POSTed to the url
22 - NSMutableDictionary *postData; 22 + NSMutableArray *postData;
23 23
24 // Files that will be POSTed to the url 24 // Files that will be POSTed to the url
25 - NSMutableDictionary *fileData; 25 + NSMutableArray *fileData;
26 26
27 ASIPostFormat postFormat; 27 ASIPostFormat postFormat;
28 28
@@ -41,15 +41,30 @@ typedef enum _ASIPostFormat { @@ -41,15 +41,30 @@ typedef enum _ASIPostFormat {
41 #pragma mark setup request 41 #pragma mark setup request
42 42
43 // Add a POST variable to the request 43 // Add a POST variable to the request
  44 +- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key;
  45 +
  46 +// Set a POST variable for this request, clearing any others with the same key
44 - (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key; 47 - (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key;
45 48
46 // Add the contents of a local file to the request 49 // Add the contents of a local file to the request
  50 +- (void)addFile:(NSString *)filePath forKey:(NSString *)key;
  51 +
  52 +// Same as above, but you can specify the content-type and file name
  53 +- (void)addFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
  54 +
  55 +// Add the contents of a local file to the request, clearing any others with the same key
47 - (void)setFile:(NSString *)filePath forKey:(NSString *)key; 56 - (void)setFile:(NSString *)filePath forKey:(NSString *)key;
48 57
49 // Same as above, but you can specify the content-type and file name 58 // Same as above, but you can specify the content-type and file name
50 - (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key; 59 - (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
51 60
52 // Add the contents of an NSData object to the request 61 // Add the contents of an NSData object to the request
  62 +- (void)addData:(NSData *)data forKey:(NSString *)key;
  63 +
  64 +// Same as above, but you can specify the content-type and file name
  65 +- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
  66 +
  67 +// Add the contents of an NSData object to the request, clearing any others with the same key
53 - (void)setData:(NSData *)data forKey:(NSString *)key; 68 - (void)setData:(NSData *)data forKey:(NSString *)key;
54 69
55 // Same as above, but you can specify the content-type and file name 70 // Same as above, but you can specify the content-type and file name
@@ -15,8 +15,8 @@ @@ -15,8 +15,8 @@
15 - (void)buildURLEncodedPostBody; 15 - (void)buildURLEncodedPostBody;
16 - (void)appendPostString:(NSString *)string; 16 - (void)appendPostString:(NSString *)string;
17 17
18 -@property (retain) NSMutableDictionary *postData; 18 +@property (retain) NSMutableArray *postData;
19 -@property (retain) NSMutableDictionary *fileData; 19 +@property (retain) NSMutableArray *fileData;
20 20
21 #if DEBUG_FORM_DATA_REQUEST 21 #if DEBUG_FORM_DATA_REQUEST
22 - (void)addToDebugBody:(NSString *)string; 22 - (void)addToDebugBody:(NSString *)string;
@@ -65,23 +65,38 @@ @@ -65,23 +65,38 @@
65 65
66 #pragma mark setup request 66 #pragma mark setup request
67 67
68 -- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key 68 +- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key
69 { 69 {
70 if (![self postData]) { 70 if (![self postData]) {
71 - [self setPostData:[NSMutableDictionary dictionary]]; 71 + [self setPostData:[NSMutableArray array]];
72 } 72 }
73 - [[self postData] setValue:[value description] forKey:key]; 73 + [[self postData] addObject:[NSDictionary dictionaryWithObjectsAndKeys:[value description],@"value",key,@"key",nil]];
74 } 74 }
75 75
76 -- (void)setFile:(NSString *)filePath forKey:(NSString *)key 76 +- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key
77 { 77 {
78 - [self setFile:filePath withFileName:nil andContentType:nil forKey:key]; 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];
79 } 88 }
80 89
81 -- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key 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
82 { 97 {
83 if (![self fileData]) { 98 if (![self fileData]) {
84 - [self setFileData:[NSMutableDictionary dictionary]]; 99 + [self setFileData:[NSMutableArray array]];
85 } 100 }
86 101
87 // If data is a path to a local file 102 // If data is a path to a local file
@@ -96,33 +111,72 @@ @@ -96,33 +111,72 @@
96 if (!fileName) { 111 if (!fileName) {
97 fileName = [(NSString *)data lastPathComponent]; 112 fileName = [(NSString *)data lastPathComponent];
98 } 113 }
99 - 114 +
100 // 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 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
101 if (!contentType) { 116 if (!contentType) {
102 contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data]; 117 contentType = [ASIHTTPRequest mimeTypeForFileAtPath:data];
103 } 118 }
104 } 119 }
105 120
106 - NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", nil]; 121 + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil];
107 - [[self fileData] setObject:fileInfo forKey:key]; 122 + [[self fileData] addObject:fileInfo];
108 } 123 }
109 124
110 -- (void)setData:(NSData *)data forKey:(NSString *)key 125 +
  126 +- (void)setFile:(NSString *)filePath forKey:(NSString *)key
111 { 127 {
112 - [self setData:data withFileName:@"file" andContentType:nil forKey:key]; 128 + [self setFile:filePath withFileName:nil andContentType:nil forKey:key];
113 } 129 }
114 130
115 -- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key 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
116 { 151 {
117 if (![self fileData]) { 152 if (![self fileData]) {
118 - [self setFileData:[NSMutableDictionary dictionary]]; 153 + [self setFileData:[NSMutableArray array]];
119 } 154 }
120 if (!contentType) { 155 if (!contentType) {
121 contentType = @"application/octet-stream"; 156 contentType = @"application/octet-stream";
122 } 157 }
123 158
124 - NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", nil]; 159 + NSDictionary *fileInfo = [NSDictionary dictionaryWithObjectsAndKeys:data, @"data", contentType, @"contentType", fileName, @"fileName", key, @"key", nil];
125 - [[self fileData] setObject:fileInfo forKey:key]; 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];
126 } 180 }
127 181
128 - (void)buildPostBody 182 - (void)buildPostBody
@@ -178,12 +232,10 @@ @@ -178,12 +232,10 @@
178 232
179 // Adds post data 233 // Adds post data
180 NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; 234 NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary];
181 - NSEnumerator *e = [[self postData] keyEnumerator];  
182 - NSString *key;  
183 NSUInteger i=0; 235 NSUInteger i=0;
184 - while (key = [e nextObject]) { 236 + for (NSDictionary *val in [self postData]) {
185 - [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key]]; 237 + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]];
186 - [self appendPostString:[[self postData] objectForKey:key]]; 238 + [self appendPostString:[val objectForKey:@"value"]];
187 i++; 239 i++;
188 if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body 240 if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body
189 [self appendPostString:endItemBoundary]; 241 [self appendPostString:endItemBoundary];
@@ -191,21 +243,17 @@ @@ -191,21 +243,17 @@
191 } 243 }
192 244
193 // Adds files to upload 245 // Adds files to upload
194 - e = [fileData keyEnumerator];  
195 i=0; 246 i=0;
196 - while (key = [e nextObject]) { 247 + for (NSDictionary *val in [self fileData]) {
197 - NSDictionary *fileInfo = [[self fileData] objectForKey:key]; 248 +
198 - id file = [fileInfo objectForKey:@"data"]; 249 + [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]];
199 - NSString *contentType = [fileInfo objectForKey:@"contentType"]; 250 + [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]];
200 - NSString *fileName = [fileInfo objectForKey:@"fileName"];  
201 -  
202 - [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", key, fileName]];  
203 - [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", contentType]];  
204 251
205 - if ([file isKindOfClass:[NSString class]]) { 252 + id data = [val objectForKey:@"data"];
206 - [self appendPostDataFromFile:file]; 253 + if ([data isKindOfClass:[NSString class]]) {
  254 + [self appendPostDataFromFile:data];
207 } else { 255 } else {
208 - [self appendPostData:file]; 256 + [self appendPostData:data];
209 } 257 }
210 i++; 258 i++;
211 // Only add the boundary if this is not the last item in the post body 259 // Only add the boundary if this is not the last item in the post body
@@ -241,12 +289,10 @@ @@ -241,12 +289,10 @@
241 [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; 289 [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
242 290
243 291
244 - NSEnumerator *e = [[self postData] keyEnumerator];  
245 - NSString *key;  
246 NSUInteger i=0; 292 NSUInteger i=0;
247 NSUInteger count = [[self postData] count]-1; 293 NSUInteger count = [[self postData] count]-1;
248 - while (key = [e nextObject]) { 294 + for (NSDictionary *val in [self postData]) {
249 - NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:key], [self encodeURL:[[self postData] objectForKey:key]],(i<count ? @"&" : @"")]; 295 + NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")];
250 [self appendPostString:data]; 296 [self appendPostString:data];
251 i++; 297 i++;
252 } 298 }
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 23
24 24
25 // Automatically set on build 25 // Automatically set on build
26 -NSString *ASIHTTPRequestVersion = @"v1.6.2-8 2010-05-01"; 26 +NSString *ASIHTTPRequestVersion = @"v1.6.2-9 2010-05-10";
27 27
28 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 28 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
29 29
@@ -227,5 +227,44 @@ @@ -227,5 +227,44 @@
227 [request2 release]; 227 [request2 release];
228 } 228 }
229 229
  230 +- (void)testMultipleValuesForASingleKey
  231 +{
  232 + ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/multiple-values"]];
  233 + [request addPostValue:@"here" forKey:@"test_value[]"];
  234 + [request addPostValue:@"are" forKey:@"test_value[]"];
  235 + [request addPostValue:@"some" forKey:@"test_value[]"];
  236 + [request addPostValue:@"values" forKey:@"test_value[]"];
  237 +
  238 + NSString *path1 = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"file1.txt"];
  239 + NSString *path2 = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"file2.txt"];
  240 + [@"hello" writeToFile:path1 atomically:NO encoding:NSUTF8StringEncoding error:nil];
  241 + [@"there" writeToFile:path2 atomically:NO encoding:NSUTF8StringEncoding error:nil];
  242 + [request addFile:path1 forKey:@"test_file[]"];
  243 + [request addFile:path2 forKey:@"test_file[]"];
  244 +
  245 + [request startSynchronous];
  246 + NSString *expectedOutput = @"here\r\nare\r\nsome\r\nvalues\r\nfile1.txt\r\nfile2.txt\r\n";
  247 + BOOL success = [[request responseString] isEqualToString:expectedOutput];
  248 + GHAssertTrue(success,@"Failed to send the correct data");
  249 +
  250 + // Check data replaces older data
  251 + request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/single-values"]];
  252 + [request addPostValue:@"here" forKey:@"test_value[]"];
  253 + [request addPostValue:@"are" forKey:@"test_value[]"];
  254 + [request addPostValue:@"some" forKey:@"test_value[]"];
  255 + [request addPostValue:@"values" forKey:@"test_value[]"];
  256 +
  257 + [request setPostValue:@"this is new data" forKey:@"test_value[]"];
  258 +
  259 + [request addFile:path1 forKey:@"test_file[]"];
  260 + [request addFile:path2 forKey:@"test_file[]"];
  261 +
  262 + [request setData:[@"this is new data" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"test_file[]"];
  263 +
  264 + [request startSynchronous];
  265 + expectedOutput = @"this is new data\r\nfile\r\n";
  266 + success = [[request responseString] isEqualToString:expectedOutput];
  267 + GHAssertTrue(success,@"Failed to send the correct data");
  268 +}
230 269
231 @end 270 @end