michael@0: // Copyright (c) 2006, Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: #import "HTTPMultipartUpload.h" michael@0: #import "GTMDefines.h" michael@0: michael@0: @interface HTTPMultipartUpload(PrivateMethods) michael@0: - (NSString *)multipartBoundary; michael@0: // Each of the following methods will append the starting multipart boundary, michael@0: // but not the ending one. michael@0: - (NSData *)formDataForKey:(NSString *)key value:(NSString *)value; michael@0: - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name; michael@0: - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name; michael@0: @end michael@0: michael@0: @implementation HTTPMultipartUpload michael@0: //============================================================================= michael@0: #pragma mark - michael@0: #pragma mark || Private || michael@0: //============================================================================= michael@0: - (NSString *)multipartBoundary { michael@0: // The boundary has 27 '-' characters followed by 16 hex digits michael@0: return [NSString stringWithFormat:@"---------------------------%08X%08X", michael@0: rand(), rand()]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { michael@0: NSString *escaped = michael@0: [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; michael@0: NSString *fmt = michael@0: @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; michael@0: NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value]; michael@0: michael@0: return [form dataUsingEncoding:NSUTF8StringEncoding]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name { michael@0: NSMutableData *data = [NSMutableData data]; michael@0: NSString *escaped = michael@0: [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; michael@0: NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; " michael@0: "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n"; michael@0: NSString *pre = [NSString stringWithFormat:fmt, boundary_, escaped]; michael@0: michael@0: [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; michael@0: [data appendData:contents]; michael@0: michael@0: return data; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSData *)formDataForFile:(NSString *)file name:(NSString *)name { michael@0: NSData *contents = [NSData dataWithContentsOfFile:file]; michael@0: michael@0: return [self formDataForFileContents:contents name:name]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: #pragma mark - michael@0: #pragma mark || Public || michael@0: //============================================================================= michael@0: - (id)initWithURL:(NSURL *)url { michael@0: if ((self = [super init])) { michael@0: url_ = [url copy]; michael@0: boundary_ = [[self multipartBoundary] retain]; michael@0: files_ = [[NSMutableDictionary alloc] init]; michael@0: } michael@0: michael@0: return self; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (void)dealloc { michael@0: [url_ release]; michael@0: [parameters_ release]; michael@0: [files_ release]; michael@0: [boundary_ release]; michael@0: [response_ release]; michael@0: michael@0: [super dealloc]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSURL *)URL { michael@0: return url_; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (void)setParameters:(NSDictionary *)parameters { michael@0: if (parameters != parameters_) { michael@0: [parameters_ release]; michael@0: parameters_ = [parameters copy]; michael@0: } michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSDictionary *)parameters { michael@0: return parameters_; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (void)addFileAtPath:(NSString *)path name:(NSString *)name { michael@0: [files_ setObject:path forKey:name]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (void)addFileContents:(NSData *)data name:(NSString *)name { michael@0: [files_ setObject:data forKey:name]; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSDictionary *)files { michael@0: return files_; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSData *)send:(NSError **)error { michael@0: NSMutableURLRequest *req = michael@0: [[NSMutableURLRequest alloc] michael@0: initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy michael@0: timeoutInterval:10.0 ]; michael@0: michael@0: NSMutableData *postBody = [NSMutableData data]; michael@0: michael@0: [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", michael@0: boundary_] forHTTPHeaderField:@"Content-type"]; michael@0: michael@0: // Add any parameters to the message michael@0: NSArray *parameterKeys = [parameters_ allKeys]; michael@0: NSString *key; michael@0: michael@0: NSInteger count = [parameterKeys count]; michael@0: for (NSInteger i = 0; i < count; ++i) { michael@0: key = [parameterKeys objectAtIndex:i]; michael@0: [postBody appendData:[self formDataForKey:key michael@0: value:[parameters_ objectForKey:key]]]; michael@0: } michael@0: michael@0: // Add any files to the message michael@0: NSArray *fileNames = [files_ allKeys]; michael@0: count = [fileNames count]; michael@0: for (NSInteger i = 0; i < count; ++i) { michael@0: NSString *name = [fileNames objectAtIndex:i]; michael@0: id fileOrData = [files_ objectForKey:name]; michael@0: NSData *fileData; michael@0: michael@0: // The object can be either the path to a file (NSString) or the contents michael@0: // of the file (NSData). michael@0: if ([fileOrData isKindOfClass:[NSData class]]) michael@0: fileData = [self formDataForFileContents:fileOrData name:name]; michael@0: else michael@0: fileData = [self formDataForFile:fileOrData name:name]; michael@0: michael@0: [postBody appendData:fileData]; michael@0: } michael@0: michael@0: NSString *epilogue = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary_]; michael@0: [postBody appendData:[epilogue dataUsingEncoding:NSUTF8StringEncoding]]; michael@0: michael@0: [req setHTTPBody:postBody]; michael@0: [req setHTTPMethod:@"POST"]; michael@0: michael@0: [response_ release]; michael@0: response_ = nil; michael@0: michael@0: NSData *data = [NSURLConnection sendSynchronousRequest:req michael@0: returningResponse:&response_ michael@0: error:error]; michael@0: michael@0: [response_ retain]; michael@0: [req release]; michael@0: michael@0: return data; michael@0: } michael@0: michael@0: //============================================================================= michael@0: - (NSHTTPURLResponse *)response { michael@0: return response_; michael@0: } michael@0: michael@0: @end