diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 18ab495..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 @@ -250,20 +250,24 @@ - (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; + +- (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 57e96ef..581aae5 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -27,22 +27,22 @@ 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 AFMutableMultipartFormData : NSObject { +@interface AFMultipartFormDataProxy : NSObject { @private NSStringEncoding _stringEncoding; - NSMutableArray *_mutableLines; + NSMutableData *_mutableData; } -- (id)initWithStringEncoding:(NSStringEncoding)encoding; +@property (readonly) NSData *data; -- (NSData *)data; +- (id)initWithStringEncoding:(NSStringEncoding)encoding; @end @@ -200,18 +200,22 @@ 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]; 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:data]; + } if (block) { block(formData); @@ -269,16 +273,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, retain) NSMutableArray *mutableLines; +@property (readwrite, nonatomic, retain) NSMutableData *mutableData; - (void)appendBlankLine; @end -@implementation AFMutableMultipartFormData +@implementation AFMultipartFormDataProxy @synthesize stringEncoding = _stringEncoding; -@synthesize mutableLines = _mutableLines; +@synthesize mutableData = _mutableData; - (id)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; @@ -287,64 +291,82 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS } self.stringEncoding = encoding; - self.mutableLines = [NSMutableArray array]; + self.mutableData = [NSMutableData dataWithLength:0]; return self; } - (void)dealloc { - [_mutableLines release]; + [_mutableData release]; [super dealloc]; } - (NSData *)data { - if ([self.mutableLines count] == 0) { - return nil; - } - - return [[[[self.mutableLines componentsJoinedByString:kAFMultipartFormLineDelimiter] stringByAppendingString:kAFMultipartFormLineDelimiter] stringByAppendingString:AFMultipartFormFinalBoundary()] dataUsingEncoding:self.stringEncoding]; + 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.mutableLines count] > 0) { - [self appendString:AFMultipartFormEncapsulationBoundary()]; - } + + [self appendString:AFMultipartFormEncapsulationBoundary()]; for (NSString *field in [headers allKeys]) { - [self appendString:[NSString stringWithFormat:@"%@: %@", field, [headers valueForKey:field]]]; + [self appendString:[NSString stringWithFormat:@"%@: %@%@", field, [headers valueForKey:field], kAFMultipartFormLineDelimiter]]; } [self appendBlankLine]; [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)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 appendString:[[[NSString alloc] initWithData:data encoding:self.stringEncoding] autorelease]]; + [self.mutableData appendData:data]; } - (void)appendString:(NSString *)string { - [self.mutableLines addObject:string]; + [self appendData:[string dataUsingEncoding:self.stringEncoding]]; } - (void)appendBlankLine { - [self appendString:@""]; + [self appendString:kAFMultipartFormLineDelimiter]; } @end