1// Copyright (c) 2006, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30#import "HTTPMultipartUpload.h" 31#import "GTMDefines.h" 32 33@interface HTTPMultipartUpload(PrivateMethods) 34- (NSString *)multipartBoundary; 35// Each of the following methods will append the starting multipart boundary, 36// but not the ending one. 37- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value; 38- (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name; 39- (NSData *)formDataForFile:(NSString *)file name:(NSString *)name; 40@end 41 42@implementation HTTPMultipartUpload 43//============================================================================= 44#pragma mark - 45#pragma mark || Private || 46//============================================================================= 47- (NSString *)multipartBoundary { 48 // The boundary has 27 '-' characters followed by 16 hex digits 49 return [NSString stringWithFormat:@"---------------------------%08X%08X", 50 rand(), rand()]; 51} 52 53//============================================================================= 54- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { 55 NSString *escaped = 56 [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 57 NSString *fmt = 58 @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; 59 NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value]; 60 61 return [form dataUsingEncoding:NSUTF8StringEncoding]; 62} 63 64//============================================================================= 65- (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name { 66 NSMutableData *data = [NSMutableData data]; 67 NSString *escaped = 68 [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 69 NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; " 70 "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n"; 71 NSString *pre = [NSString stringWithFormat:fmt, boundary_, escaped]; 72 73 [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; 74 [data appendData:contents]; 75 76 return data; 77} 78 79//============================================================================= 80- (NSData *)formDataForFile:(NSString *)file name:(NSString *)name { 81 NSData *contents = [NSData dataWithContentsOfFile:file]; 82 83 return [self formDataForFileContents:contents name:name]; 84} 85 86//============================================================================= 87#pragma mark - 88#pragma mark || Public || 89//============================================================================= 90- (id)initWithURL:(NSURL *)url { 91 if ((self = [super init])) { 92 url_ = [url copy]; 93 boundary_ = [[self multipartBoundary] retain]; 94 files_ = [[NSMutableDictionary alloc] init]; 95 } 96 97 return self; 98} 99 100//============================================================================= 101- (void)dealloc { 102 [url_ release]; 103 [parameters_ release]; 104 [files_ release]; 105 [boundary_ release]; 106 [response_ release]; 107 108 [super dealloc]; 109} 110 111//============================================================================= 112- (NSURL *)URL { 113 return url_; 114} 115 116//============================================================================= 117- (void)setParameters:(NSDictionary *)parameters { 118 if (parameters != parameters_) { 119 [parameters_ release]; 120 parameters_ = [parameters copy]; 121 } 122} 123 124//============================================================================= 125- (NSDictionary *)parameters { 126 return parameters_; 127} 128 129//============================================================================= 130- (void)addFileAtPath:(NSString *)path name:(NSString *)name { 131 [files_ setObject:path forKey:name]; 132} 133 134//============================================================================= 135- (void)addFileContents:(NSData *)data name:(NSString *)name { 136 [files_ setObject:data forKey:name]; 137} 138 139//============================================================================= 140- (NSDictionary *)files { 141 return files_; 142} 143 144//============================================================================= 145- (NSData *)send:(NSError **)error { 146 NSMutableURLRequest *req = 147 [[NSMutableURLRequest alloc] 148 initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy 149 timeoutInterval:10.0 ]; 150 151 NSMutableData *postBody = [NSMutableData data]; 152 153 [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", 154 boundary_] forHTTPHeaderField:@"Content-type"]; 155 156 // Add any parameters to the message 157 NSArray *parameterKeys = [parameters_ allKeys]; 158 NSString *key; 159 160 NSInteger count = [parameterKeys count]; 161 for (NSInteger i = 0; i < count; ++i) { 162 key = [parameterKeys objectAtIndex:i]; 163 [postBody appendData:[self formDataForKey:key 164 value:[parameters_ objectForKey:key]]]; 165 } 166 167 // Add any files to the message 168 NSArray *fileNames = [files_ allKeys]; 169 count = [fileNames count]; 170 for (NSInteger i = 0; i < count; ++i) { 171 NSString *name = [fileNames objectAtIndex:i]; 172 id fileOrData = [files_ objectForKey:name]; 173 NSData *fileData; 174 175 // The object can be either the path to a file (NSString) or the contents 176 // of the file (NSData). 177 if ([fileOrData isKindOfClass:[NSData class]]) 178 fileData = [self formDataForFileContents:fileOrData name:name]; 179 else 180 fileData = [self formDataForFile:fileOrData name:name]; 181 182 [postBody appendData:fileData]; 183 } 184 185 NSString *epilogue = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary_]; 186 [postBody appendData:[epilogue dataUsingEncoding:NSUTF8StringEncoding]]; 187 188 [req setHTTPBody:postBody]; 189 [req setHTTPMethod:@"POST"]; 190 191 [response_ release]; 192 response_ = nil; 193 194 NSData *data = nil; 195 if ([[req URL] isFileURL]) { 196 [[req HTTPBody] writeToURL:[req URL] options:0 error:error]; 197 } else { 198 data = [NSURLConnection sendSynchronousRequest:req 199 returningResponse:&response_ 200 error:error]; 201 [response_ retain]; 202 } 203 [req release]; 204 205 return data; 206} 207 208//============================================================================= 209- (NSHTTPURLResponse *)response { 210 return response_; 211} 212 213@end 214