From b621ad08481015a64500ca979e2155cc55249a8a Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 23 Sep 2011 10:21:07 -0500 Subject: [PATCH 1/3] Changing multipart form data object to be subclass of NSMutableData Adding mimetype arguments to some multipart methods --- AFNetworking/AFHTTPClient.h | 16 +++++---- AFNetworking/AFHTTPClient.m | 70 ++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 3c1fc3d..b3c3b60 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -250,20 +250,22 @@ - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body; /** - Appends the HTTP header `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary. + Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"` and, if mimeType is specified, `Content-Type: #{mimeType}`, followed by the encoded data and the multipart form boundary. @param data The data to be encoded and appended to the form data. - @param name The name to be associated with the specified data. + @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. If `nil`, the `Content-Type` header will be omitted. + @param name The name to be associated with the specified data. This parameter must not be `nil`. */ -- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; +- (void)appendPartWithFormData:(NSData *)data mimeType:(NSString *)mimeType name:(NSString *)name; /** - Appends the HTTP header `Content-Disposition: file; filename=#{filename}"`, followed by the encoded file data and the multipart form boundary. + Appends the HTTP header `Content-Disposition: file; filename=#{filename}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. - @param fileURL The URL for the local file to have its contents appended to the form data. - @param fileNameOrNil The filename to be associated with the file contents. If `nil`, the last path component followed by its file extension will be used instead. + @param fileURL The URL for the local file to have its contents appended to the form data. This parameter must not be `nil`. + @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`. + @param fileName The filename to be associated with the file contents. This parameter must not be `nil`. */ -- (void)appendPartWithFile:(NSURL *)fileURL fileName:(NSString *)fileNameOrNil; +- (void)appendPartWithFile:(NSURL *)fileURL mimeType:(NSString *)mimeType fileName:(NSString *)fileName; /** Appends encoded data to the form data. diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 9ba7613..52818ea 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -34,16 +34,14 @@ static NSString * AFMultipartFormFinalBoundary() { return [NSString stringWithFormat:@"--%@--", kAFMultipartFormBoundary]; } -@interface AFMutableMultipartFormData : NSObject { +@interface AFMutableMultipartFormData : NSMutableData { @private NSStringEncoding _stringEncoding; - NSMutableArray *_mutableLines; + NSRange _finalBoundaryRange; } - (id)initWithStringEncoding:(NSStringEncoding)encoding; -- (NSData *)data; - @end #pragma mark - @@ -207,19 +205,23 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS NSEnumerator *enumerator = [parameters keyEnumerator]; while ((key = [enumerator nextObject])) { id value = [parameters valueForKey:key]; - if (![value isKindOfClass:[NSData class]]) { - value = [value description]; + NSData *data = nil; + + if ([value isKindOfClass:[NSData class]]) { + data = value; + } else { + data = [[value description] dataUsingEncoding:self.stringEncoding]; } - [formData appendPartWithFormData:[value dataUsingEncoding:self.stringEncoding] name:[key description]]; - } + [formData appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", [key description]] forKey:@"Content-Disposition"] body:value]; + } if (block) { block(formData); } [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:[formData data]]; + [request setHTTPBody:formData]; [formData autorelease]; @@ -272,14 +274,14 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS // multipart/form-data; see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 @interface AFMutableMultipartFormData () @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; -@property (readwrite, nonatomic, retain) NSMutableArray *mutableLines; +@property (readwrite, nonatomic, assign) NSRange finalBoundaryRange; - (void)appendBlankLine; @end @implementation AFMutableMultipartFormData @synthesize stringEncoding = _stringEncoding; -@synthesize mutableLines = _mutableLines; +@synthesize finalBoundaryRange = _finalBoundaryRange; - (id)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; @@ -288,28 +290,15 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } self.stringEncoding = encoding; - self.mutableLines = [NSMutableArray array]; + self.finalBoundaryRange = NSMakeRange(0, 0); return self; } -- (void)dealloc { - [_mutableLines release]; - [super dealloc]; -} - -- (NSData *)data { - if ([self.mutableLines count] == 0) { - return nil; - } - - return [[[[self.mutableLines componentsJoinedByString:kAFMultipartFormLineDelimiter] stringByAppendingString:kAFMultipartFormLineDelimiter] stringByAppendingString:AFMultipartFormFinalBoundary()] dataUsingEncoding:self.stringEncoding]; -} - #pragma mark - AFMultipartFormDataProxy - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { - if ([self.mutableLines count] > 0) { + if ([self length] > 0) { [self appendString:AFMultipartFormEncapsulationBoundary()]; } @@ -321,27 +310,42 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS [self appendData:body]; } -- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name { - [self appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"] body:data]; +- (void)appendPartWithFormData:(NSData *)data mimeType:(NSString *)mimeType name:(NSString *)name { + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; + if (mimeType) { + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + } + + [self appendPartWithHeaders:mutableHeaders body:data]; } -- (void)appendPartWithFile:(NSURL *)fileURL fileName:(NSString *)fileNameOrNil { +- (void)appendPartWithFile:(NSURL *)fileURL mimeType:(NSString *)mimeType fileName:(NSString *)fileName { if (![fileURL isFileURL]) { [NSException raise:@"Invalid fileURL value" format:@"%@ must be a valid file URL", fileURL]; return; } + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"file; filename=\"%@\"", fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + NSData *data = [NSData dataWithContentsOfFile:[fileURL absoluteString]]; - NSString *fileName = fileNameOrNil ? fileNameOrNil : [[fileURL lastPathComponent] stringByAppendingPathExtension:[fileURL pathExtension]]; - [self appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"file; filename=\"%@\"", fileName] forKey:@"Content-Disposition"] body:data]; + + [self appendPartWithHeaders:mutableHeaders body:data]; } - (void)appendData:(NSData *)data { - [self appendString:[[[NSString alloc] initWithData:data encoding:self.stringEncoding] autorelease]]; + NSMutableData *mutableData = [NSMutableData dataWithData:data]; + [self replaceBytesInRange:self.finalBoundaryRange withBytes:[mutableData bytes]]; + + NSData *finalBoundary = [AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]; + self.finalBoundaryRange = NSMakeRange([self length], [finalBoundary length]); + [super appendData:finalBoundary]; } - (void)appendString:(NSString *)string { - [self.mutableLines addObject:string]; + [self appendData:[string dataUsingEncoding:self.stringEncoding]]; } - (void)appendBlankLine { From 0c0b1bd383d4908d84a957b376afbc13f120a714 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 23 Sep 2011 14:25:04 -0500 Subject: [PATCH 2/3] Using composition of NSMutableData rather than subclassing Renaming private form data proxy object to match @protocol name --- AFNetworking/AFHTTPClient.m | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 52818ea..6636943 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -34,12 +34,14 @@ static NSString * AFMultipartFormFinalBoundary() { return [NSString stringWithFormat:@"--%@--", kAFMultipartFormBoundary]; } -@interface AFMutableMultipartFormData : NSMutableData { +@interface AFMultipartFormDataProxy : NSObject { @private NSStringEncoding _stringEncoding; - NSRange _finalBoundaryRange; + NSMutableData *_mutableData; } +@property (readonly) NSData *data; + - (id)initWithStringEncoding:(NSStringEncoding)encoding; @end @@ -199,7 +201,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil]; - __block AFMutableMultipartFormData *formData = [[AFMutableMultipartFormData alloc] initWithStringEncoding:self.stringEncoding]; + __block AFMultipartFormDataProxy *formData = [[AFMultipartFormDataProxy alloc] initWithStringEncoding:self.stringEncoding]; id key = nil; NSEnumerator *enumerator = [parameters keyEnumerator]; @@ -213,7 +215,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS data = [[value description] dataUsingEncoding:self.stringEncoding]; } - [formData appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", [key description]] forKey:@"Content-Disposition"] body:value]; + [formData appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", [key description]] forKey:@"Content-Disposition"] body:data]; } if (block) { @@ -221,7 +223,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:formData]; + [request setHTTPBody:[formData data]]; [formData autorelease]; @@ -272,16 +274,16 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS #pragma mark - // multipart/form-data; see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 -@interface AFMutableMultipartFormData () +@interface AFMultipartFormDataProxy () @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; -@property (readwrite, nonatomic, assign) NSRange finalBoundaryRange; +@property (readwrite, nonatomic, retain) NSMutableData *mutableData; - (void)appendBlankLine; @end -@implementation AFMutableMultipartFormData +@implementation AFMultipartFormDataProxy @synthesize stringEncoding = _stringEncoding; -@synthesize finalBoundaryRange = _finalBoundaryRange; +@synthesize mutableData = _mutableData; - (id)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; @@ -290,20 +292,34 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } self.stringEncoding = encoding; - self.finalBoundaryRange = NSMakeRange(0, 0); + self.mutableData = [NSMutableData dataWithLength:0]; return self; } +- (void)dealloc { + [_mutableData release]; + [super dealloc]; +} + +- (NSData *)data { + NSMutableData *finalizedData = [NSMutableData dataWithData:self.mutableData]; + [finalizedData appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]]; + + return finalizedData; +} + #pragma mark - AFMultipartFormDataProxy - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { - if ([self length] > 0) { + if ([self.mutableData length] > 0) { [self appendString:AFMultipartFormEncapsulationBoundary()]; + [self appendBlankLine]; } for (NSString *field in [headers allKeys]) { [self appendString:[NSString stringWithFormat:@"%@: %@", field, [headers valueForKey:field]]]; + [self appendBlankLine]; } [self appendBlankLine]; @@ -336,12 +352,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } - (void)appendData:(NSData *)data { - NSMutableData *mutableData = [NSMutableData dataWithData:data]; - [self replaceBytesInRange:self.finalBoundaryRange withBytes:[mutableData bytes]]; - - NSData *finalBoundary = [AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]; - self.finalBoundaryRange = NSMakeRange([self length], [finalBoundary length]); - [super appendData:finalBoundary]; + [self.mutableData appendData:data]; } - (void)appendString:(NSString *)string { @@ -349,7 +360,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } - (void)appendBlankLine { - [self appendString:@""]; + [self appendString:kAFMultipartFormLineDelimiter]; } @end From 4f070b2924ab1112889cc18551e5a7a0b53a4b61 Mon Sep 17 00:00:00 2001 From: Tyler Richey Date: Sat, 24 Sep 2011 21:53:46 -0400 Subject: [PATCH 3/3] file data upload fix --- AFNetworking/AFHTTPClient.h | 6 ++++-- AFNetworking/AFHTTPClient.m | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index e740a4a..4039564 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -37,7 +37,7 @@ } /** - The url used as the base for paths specified in methods such as `getPath:parameters:success:failure` + The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure` */ @property (readonly, nonatomic, retain) NSURL *baseURL; @@ -49,7 +49,7 @@ /** The operation queue which manages operations enqueued by the HTTP client. */ -@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue; +@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue;; ///--------------------------------------------- /// @name Creating and Initializing HTTP Clients @@ -267,6 +267,8 @@ */ - (void)appendPartWithFile:(NSURL *)fileURL mimeType:(NSString *)mimeType fileName:(NSString *)fileName; +- (void)appendPartWithFileData:(NSData *)data mimeType:(NSString *)mimeType name:(NSString *)name; + /** Appends encoded data to the form data. diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 6636943..10bed77 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -27,11 +27,11 @@ static NSString * const kAFMultipartFormLineDelimiter = @"\r\n"; // CRLF static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY"; static NSString * AFMultipartFormEncapsulationBoundary() { - return [NSString stringWithFormat:@"--%@", kAFMultipartFormBoundary]; + return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormLineDelimiter, kAFMultipartFormBoundary, kAFMultipartFormLineDelimiter]; } static NSString * AFMultipartFormFinalBoundary() { - return [NSString stringWithFormat:@"--%@--", kAFMultipartFormBoundary]; + return [NSString stringWithFormat:@"%@--%@--", kAFMultipartFormLineDelimiter, kAFMultipartFormBoundary]; } @interface AFMultipartFormDataProxy : NSObject { @@ -305,21 +305,17 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS - (NSData *)data { NSMutableData *finalizedData = [NSMutableData dataWithData:self.mutableData]; [finalizedData appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]]; - return finalizedData; } #pragma mark - AFMultipartFormDataProxy - (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { - if ([self.mutableData length] > 0) { - [self appendString:AFMultipartFormEncapsulationBoundary()]; - [self appendBlankLine]; - } + + [self appendString:AFMultipartFormEncapsulationBoundary()]; for (NSString *field in [headers allKeys]) { - [self appendString:[NSString stringWithFormat:@"%@: %@", field, [headers valueForKey:field]]]; - [self appendBlankLine]; + [self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormLineDelimiter]]; } [self appendBlankLine]; @@ -351,6 +347,17 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS [self appendPartWithHeaders:mutableHeaders body:data]; } +- (void)appendPartWithFileData:(NSData *)data mimeType:(NSString *)mimeType name:(NSString *)name { + + NSString *fileName = [[NSString stringWithFormat:@"%d", [[NSDate date] hash]] stringByAppendingPathExtension:[mimeType lastPathComponent]]; + + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"file; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + + [self appendPartWithHeaders:mutableHeaders body:data]; +} + - (void)appendData:(NSData *)data { [self.mutableData appendData:data]; }