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