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// As -[NSString stringByAddingPercentEscapesUsingEncoding:] has been 34// deprecated with iOS 9.0 / OS X 10.11 SDKs, this function re-implements it 35// using -[NSString stringByAddingPercentEncodingWithAllowedCharacters:] when 36// using those SDKs. 37static NSString *PercentEncodeNSString(NSString *key) { 38#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_9_0) && \ 39 __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0) || \ 40 (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ 41 defined(MAC_OS_X_VERSION_10_11) && \ 42 MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) 43 return [key stringByAddingPercentEncodingWithAllowedCharacters: 44 [NSCharacterSet URLQueryAllowedCharacterSet]]; 45#else 46 return [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 47#endif 48} 49 50// As -[NSURLConnection sendSynchronousRequest:returningResponse:error:] has 51// been deprecated with iOS 9.0 / OS X 10.11 SDKs, this function re-implements 52// it using -[NSURLSession dataTaskWithRequest:completionHandler:] which is 53// available on iOS 7+. 54static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, 55 NSURLResponse **out_response, 56 NSError **out_error) { 57#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ 58 __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) || \ 59 (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ 60 defined(MAC_OS_X_VERSION_10_11) && \ 61 MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) 62 __block NSData* result = nil; 63 __block NSError* error = nil; 64 __block NSURLResponse* response = nil; 65 dispatch_semaphore_t wait_semaphone = dispatch_semaphore_create(0); 66 [[[NSURLSession sharedSession] 67 dataTaskWithRequest:req 68 completionHandler:^(NSData *data, 69 NSURLResponse *resp, 70 NSError *err) { 71 if (out_error) 72 error = [err retain]; 73 if (out_response) 74 response = [resp retain]; 75 if (err == nil) 76 result = [data retain]; 77 dispatch_semaphore_signal(wait_semaphone); 78 }] resume]; 79 dispatch_semaphore_wait(wait_semaphone, DISPATCH_TIME_FOREVER); 80 dispatch_release(wait_semaphone); 81 if (out_error) 82 *out_error = [error autorelease]; 83 if (out_response) 84 *out_response = [response autorelease]; 85 return [result autorelease]; 86#else 87 return [NSURLConnection sendSynchronousRequest:req 88 returningResponse:out_response 89 error:out_error]; 90#endif 91} 92@interface HTTPMultipartUpload(PrivateMethods) 93- (NSString *)multipartBoundary; 94// Each of the following methods will append the starting multipart boundary, 95// but not the ending one. 96- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value; 97- (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name; 98- (NSData *)formDataForFile:(NSString *)file name:(NSString *)name; 99@end 100 101@implementation HTTPMultipartUpload 102//============================================================================= 103#pragma mark - 104#pragma mark || Private || 105//============================================================================= 106- (NSString *)multipartBoundary { 107 // The boundary has 27 '-' characters followed by 16 hex digits 108 return [NSString stringWithFormat:@"---------------------------%08X%08X", 109 rand(), rand()]; 110} 111 112//============================================================================= 113- (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { 114 NSString *escaped = PercentEncodeNSString(key); 115 NSString *fmt = 116 @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; 117 NSString *form = [NSString stringWithFormat:fmt, boundary_, escaped, value]; 118 119 return [form dataUsingEncoding:NSUTF8StringEncoding]; 120} 121 122//============================================================================= 123- (NSData *)formDataForFileContents:(NSData *)contents name:(NSString *)name { 124 NSMutableData *data = [NSMutableData data]; 125 NSString *escaped = PercentEncodeNSString(name); 126 NSString *fmt = @"--%@\r\nContent-Disposition: form-data; name=\"%@\"; " 127 "filename=\"minidump.dmp\"\r\nContent-Type: application/octet-stream\r\n\r\n"; 128 NSString *pre = [NSString stringWithFormat:fmt, boundary_, escaped]; 129 130 [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; 131 [data appendData:contents]; 132 133 return data; 134} 135 136//============================================================================= 137- (NSData *)formDataForFile:(NSString *)file name:(NSString *)name { 138 NSData *contents = [NSData dataWithContentsOfFile:file]; 139 140 return [self formDataForFileContents:contents name:name]; 141} 142 143//============================================================================= 144#pragma mark - 145#pragma mark || Public || 146//============================================================================= 147- (id)initWithURL:(NSURL *)url { 148 if ((self = [super init])) { 149 url_ = [url copy]; 150 boundary_ = [[self multipartBoundary] retain]; 151 files_ = [[NSMutableDictionary alloc] init]; 152 } 153 154 return self; 155} 156 157//============================================================================= 158- (void)dealloc { 159 [url_ release]; 160 [parameters_ release]; 161 [files_ release]; 162 [boundary_ release]; 163 [response_ release]; 164 165 [super dealloc]; 166} 167 168//============================================================================= 169- (NSURL *)URL { 170 return url_; 171} 172 173//============================================================================= 174- (void)setParameters:(NSDictionary *)parameters { 175 if (parameters != parameters_) { 176 [parameters_ release]; 177 parameters_ = [parameters copy]; 178 } 179} 180 181//============================================================================= 182- (NSDictionary *)parameters { 183 return parameters_; 184} 185 186//============================================================================= 187- (void)addFileAtPath:(NSString *)path name:(NSString *)name { 188 [files_ setObject:path forKey:name]; 189} 190 191//============================================================================= 192- (void)addFileContents:(NSData *)data name:(NSString *)name { 193 [files_ setObject:data forKey:name]; 194} 195 196//============================================================================= 197- (NSDictionary *)files { 198 return files_; 199} 200 201//============================================================================= 202- (NSData *)send:(NSError **)error { 203 NSMutableURLRequest *req = 204 [[NSMutableURLRequest alloc] 205 initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy 206 timeoutInterval:60.0]; 207 208 NSMutableData *postBody = [NSMutableData data]; 209 210 [req setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", 211 boundary_] forHTTPHeaderField:@"Content-type"]; 212 213 // Add any parameters to the message 214 NSArray *parameterKeys = [parameters_ allKeys]; 215 NSString *key; 216 217 NSInteger count = [parameterKeys count]; 218 for (NSInteger i = 0; i < count; ++i) { 219 key = [parameterKeys objectAtIndex:i]; 220 [postBody appendData:[self formDataForKey:key 221 value:[parameters_ objectForKey:key]]]; 222 } 223 224 // Add any files to the message 225 NSArray *fileNames = [files_ allKeys]; 226 for (NSString *name in fileNames) { 227 id fileOrData = [files_ objectForKey:name]; 228 NSData *fileData; 229 230 // The object can be either the path to a file (NSString) or the contents 231 // of the file (NSData). 232 if ([fileOrData isKindOfClass:[NSData class]]) 233 fileData = [self formDataForFileContents:fileOrData name:name]; 234 else 235 fileData = [self formDataForFile:fileOrData name:name]; 236 237 [postBody appendData:fileData]; 238 } 239 240 NSString *epilogue = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary_]; 241 [postBody appendData:[epilogue dataUsingEncoding:NSUTF8StringEncoding]]; 242 243 [req setHTTPBody:postBody]; 244 [req setHTTPMethod:@"POST"]; 245 246 [response_ release]; 247 response_ = nil; 248 249 NSData *data = nil; 250 if ([[req URL] isFileURL]) { 251 [[req HTTPBody] writeToURL:[req URL] options:0 error:error]; 252 } else { 253 NSURLResponse *response = nil; 254 data = SendSynchronousNSURLRequest(req, &response, error); 255 response_ = (NSHTTPURLResponse *)[response retain]; 256 } 257 [req release]; 258 259 return data; 260} 261 262//============================================================================= 263- (NSHTTPURLResponse *)response { 264 return response_; 265} 266 267@end 268