1*9712c20fSFrederick Mayle// Copyright 2020 Google LLC 2*9712c20fSFrederick Mayle// 3*9712c20fSFrederick Mayle// Redistribution and use in source and binary forms, with or without 4*9712c20fSFrederick Mayle// modification, are permitted provided that the following conditions are 5*9712c20fSFrederick Mayle// met: 6*9712c20fSFrederick Mayle// 7*9712c20fSFrederick Mayle// * Redistributions of source code must retain the above copyright 8*9712c20fSFrederick Mayle// notice, this list of conditions and the following disclaimer. 9*9712c20fSFrederick Mayle// * Redistributions in binary form must reproduce the above 10*9712c20fSFrederick Mayle// copyright notice, this list of conditions and the following disclaimer 11*9712c20fSFrederick Mayle// in the documentation and/or other materials provided with the 12*9712c20fSFrederick Mayle// distribution. 13*9712c20fSFrederick Mayle// * Neither the name of Google LLC nor the names of its 14*9712c20fSFrederick Mayle// contributors may be used to endorse or promote products derived from 15*9712c20fSFrederick Mayle// this software without specific prior written permission. 16*9712c20fSFrederick Mayle// 17*9712c20fSFrederick Mayle// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18*9712c20fSFrederick Mayle// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19*9712c20fSFrederick Mayle// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20*9712c20fSFrederick Mayle// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21*9712c20fSFrederick Mayle// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22*9712c20fSFrederick Mayle// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23*9712c20fSFrederick Mayle// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24*9712c20fSFrederick Mayle// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25*9712c20fSFrederick Mayle// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26*9712c20fSFrederick Mayle// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27*9712c20fSFrederick Mayle// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28*9712c20fSFrederick Mayle 29*9712c20fSFrederick Mayle#import "HTTPRequest.h" 30*9712c20fSFrederick Mayle 31*9712c20fSFrederick Mayle#include <Availability.h> 32*9712c20fSFrederick Mayle#include <AvailabilityMacros.h> 33*9712c20fSFrederick Mayle 34*9712c20fSFrederick Mayle#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ 35*9712c20fSFrederick Mayle __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) 36*9712c20fSFrederick Mayle#import <UIKit/UIKit.h> 37*9712c20fSFrederick Mayle#define HAS_BACKGROUND_TASK_API 1 38*9712c20fSFrederick Mayle#else 39*9712c20fSFrederick Mayle#define HAS_BACKGROUND_TASK_API 0 40*9712c20fSFrederick Mayle#endif 41*9712c20fSFrederick Mayle 42*9712c20fSFrederick Mayle#import "encoding_util.h" 43*9712c20fSFrederick Mayle 44*9712c20fSFrederick Mayle#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ 45*9712c20fSFrederick Mayle __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) || \ 46*9712c20fSFrederick Mayle (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ 47*9712c20fSFrederick Mayle defined(MAC_OS_X_VERSION_10_11) && \ 48*9712c20fSFrederick Mayle MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) 49*9712c20fSFrederick Mayle#define USE_NSURLSESSION 1 50*9712c20fSFrederick Mayle#else 51*9712c20fSFrederick Mayle#define USE_NSURLSESSION 0 52*9712c20fSFrederick Mayle#endif 53*9712c20fSFrederick Mayle 54*9712c20fSFrederick Mayle// As -[NSURLConnection sendSynchronousRequest:returningResponse:error:] has 55*9712c20fSFrederick Mayle// been deprecated with iOS 9.0 / OS X 10.11 SDKs, this function re-implements 56*9712c20fSFrederick Mayle// it using -[NSURLSession dataTaskWithRequest:completionHandler:] which is 57*9712c20fSFrederick Mayle// available on iOS 7+. 58*9712c20fSFrederick Maylestatic NSData* SendSynchronousNSURLRequest(NSURLRequest* req, 59*9712c20fSFrederick Mayle NSURLResponse** outResponse, 60*9712c20fSFrederick Mayle NSError** outError) { 61*9712c20fSFrederick Mayle#if USE_NSURLSESSION 62*9712c20fSFrederick Mayle __block NSData* result = nil; 63*9712c20fSFrederick Mayle __block NSError* error = nil; 64*9712c20fSFrederick Mayle __block NSURLResponse* response = nil; 65*9712c20fSFrederick Mayle dispatch_semaphore_t waitSemaphone = dispatch_semaphore_create(0); 66*9712c20fSFrederick Mayle 67*9712c20fSFrederick Mayle NSURLSessionConfiguration* config = 68*9712c20fSFrederick Mayle [NSURLSessionConfiguration defaultSessionConfiguration]; 69*9712c20fSFrederick Mayle [config setTimeoutIntervalForRequest:240.0]; 70*9712c20fSFrederick Mayle NSURLSession* session = [NSURLSession sessionWithConfiguration:config]; 71*9712c20fSFrederick Mayle NSURLSessionDataTask *task = [session 72*9712c20fSFrederick Mayle dataTaskWithRequest:req 73*9712c20fSFrederick Mayle completionHandler:^(NSData* data, NSURLResponse* resp, NSError* err) { 74*9712c20fSFrederick Mayle if (outError) 75*9712c20fSFrederick Mayle error = [err retain]; 76*9712c20fSFrederick Mayle if (outResponse) 77*9712c20fSFrederick Mayle response = [resp retain]; 78*9712c20fSFrederick Mayle if (err == nil) 79*9712c20fSFrederick Mayle result = [data retain]; 80*9712c20fSFrederick Mayle dispatch_semaphore_signal(waitSemaphone); 81*9712c20fSFrederick Mayle }]; 82*9712c20fSFrederick Mayle [task resume]; 83*9712c20fSFrederick Mayle 84*9712c20fSFrederick Mayle#if HAS_BACKGROUND_TASK_API 85*9712c20fSFrederick Mayle // Used to guard against ending the background task twice, which UIKit 86*9712c20fSFrederick Mayle // considers to be an error. 87*9712c20fSFrederick Mayle __block BOOL isBackgroundTaskActive = YES; 88*9712c20fSFrederick Mayle __block UIBackgroundTaskIdentifier backgroundTaskIdentifier = 89*9712c20fSFrederick Mayle UIBackgroundTaskInvalid; 90*9712c20fSFrederick Mayle backgroundTaskIdentifier = [UIApplication.sharedApplication 91*9712c20fSFrederick Mayle beginBackgroundTaskWithName:@"Breakpad Upload" 92*9712c20fSFrederick Mayle expirationHandler:^{ 93*9712c20fSFrederick Mayle if (!isBackgroundTaskActive) { 94*9712c20fSFrederick Mayle return; 95*9712c20fSFrederick Mayle } 96*9712c20fSFrederick Mayle isBackgroundTaskActive = NO; 97*9712c20fSFrederick Mayle 98*9712c20fSFrederick Mayle [task cancel]; 99*9712c20fSFrederick Mayle [UIApplication.sharedApplication 100*9712c20fSFrederick Mayle endBackgroundTask:backgroundTaskIdentifier]; 101*9712c20fSFrederick Mayle }]; 102*9712c20fSFrederick Mayle#endif // HAS_BACKGROUND_TASK_API 103*9712c20fSFrederick Mayle 104*9712c20fSFrederick Mayle dispatch_semaphore_wait(waitSemaphone, DISPATCH_TIME_FOREVER); 105*9712c20fSFrederick Mayle dispatch_release(waitSemaphone); 106*9712c20fSFrederick Mayle 107*9712c20fSFrederick Mayle#if HAS_BACKGROUND_TASK_API 108*9712c20fSFrederick Mayle if (backgroundTaskIdentifier != UIBackgroundTaskInvalid) { 109*9712c20fSFrederick Mayle // Dispatch to main queue in order to synchronize access to 110*9712c20fSFrederick Mayle // `isBackgroundTaskActive` with the background task expiration handler, 111*9712c20fSFrederick Mayle // which is always run on the main thread. 112*9712c20fSFrederick Mayle dispatch_async(dispatch_get_main_queue(), ^{ 113*9712c20fSFrederick Mayle if (!isBackgroundTaskActive) { 114*9712c20fSFrederick Mayle return; 115*9712c20fSFrederick Mayle } 116*9712c20fSFrederick Mayle isBackgroundTaskActive = NO; 117*9712c20fSFrederick Mayle 118*9712c20fSFrederick Mayle [UIApplication.sharedApplication 119*9712c20fSFrederick Mayle endBackgroundTask:backgroundTaskIdentifier]; 120*9712c20fSFrederick Mayle }); 121*9712c20fSFrederick Mayle } 122*9712c20fSFrederick Mayle#endif // HAS_BACKGROUND_TASK_API 123*9712c20fSFrederick Mayle 124*9712c20fSFrederick Mayle if (outError) 125*9712c20fSFrederick Mayle *outError = [error autorelease]; 126*9712c20fSFrederick Mayle if (outResponse) 127*9712c20fSFrederick Mayle *outResponse = [response autorelease]; 128*9712c20fSFrederick Mayle return [result autorelease]; 129*9712c20fSFrederick Mayle#else // USE_NSURLSESSION 130*9712c20fSFrederick Mayle return [NSURLConnection sendSynchronousRequest:req 131*9712c20fSFrederick Mayle returningResponse:outResponse 132*9712c20fSFrederick Mayle error:outError]; 133*9712c20fSFrederick Mayle#endif // USE_NSURLSESSION 134*9712c20fSFrederick Mayle} 135*9712c20fSFrederick Mayle 136*9712c20fSFrederick Mayle@implementation HTTPRequest 137*9712c20fSFrederick Mayle 138*9712c20fSFrederick Mayle//============================================================================= 139*9712c20fSFrederick Mayle- (id)initWithURL:(NSURL*)URL { 140*9712c20fSFrederick Mayle if ((self = [super init])) { 141*9712c20fSFrederick Mayle URL_ = [URL copy]; 142*9712c20fSFrederick Mayle } 143*9712c20fSFrederick Mayle 144*9712c20fSFrederick Mayle return self; 145*9712c20fSFrederick Mayle} 146*9712c20fSFrederick Mayle 147*9712c20fSFrederick Mayle//============================================================================= 148*9712c20fSFrederick Mayle- (void)dealloc { 149*9712c20fSFrederick Mayle [URL_ release]; 150*9712c20fSFrederick Mayle [response_ release]; 151*9712c20fSFrederick Mayle 152*9712c20fSFrederick Mayle [super dealloc]; 153*9712c20fSFrederick Mayle} 154*9712c20fSFrederick Mayle 155*9712c20fSFrederick Mayle//============================================================================= 156*9712c20fSFrederick Mayle- (NSURL*)URL { 157*9712c20fSFrederick Mayle return URL_; 158*9712c20fSFrederick Mayle} 159*9712c20fSFrederick Mayle 160*9712c20fSFrederick Mayle//============================================================================= 161*9712c20fSFrederick Mayle- (NSHTTPURLResponse*)response { 162*9712c20fSFrederick Mayle return response_; 163*9712c20fSFrederick Mayle} 164*9712c20fSFrederick Mayle 165*9712c20fSFrederick Mayle//============================================================================= 166*9712c20fSFrederick Mayle- (NSString*)HTTPMethod { 167*9712c20fSFrederick Mayle @throw [NSException 168*9712c20fSFrederick Mayle exceptionWithName:NSInternalInconsistencyException 169*9712c20fSFrederick Mayle reason:[NSString stringWithFormat:@"You must" 170*9712c20fSFrederick Mayle "override %@ in a subclass", 171*9712c20fSFrederick Mayle NSStringFromSelector(_cmd)] 172*9712c20fSFrederick Mayle userInfo:nil]; 173*9712c20fSFrederick Mayle} 174*9712c20fSFrederick Mayle 175*9712c20fSFrederick Mayle//============================================================================= 176*9712c20fSFrederick Mayle- (NSString*)contentType { 177*9712c20fSFrederick Mayle return nil; 178*9712c20fSFrederick Mayle} 179*9712c20fSFrederick Mayle 180*9712c20fSFrederick Mayle//============================================================================= 181*9712c20fSFrederick Mayle- (NSData*)bodyData { 182*9712c20fSFrederick Mayle return nil; 183*9712c20fSFrederick Mayle} 184*9712c20fSFrederick Mayle 185*9712c20fSFrederick Mayle//============================================================================= 186*9712c20fSFrederick Mayle- (NSData*)send:(NSError**)withError { 187*9712c20fSFrederick Mayle NSMutableURLRequest* req = [[NSMutableURLRequest alloc] 188*9712c20fSFrederick Mayle initWithURL:URL_ 189*9712c20fSFrederick Mayle cachePolicy:NSURLRequestUseProtocolCachePolicy 190*9712c20fSFrederick Mayle timeoutInterval:60.0]; 191*9712c20fSFrederick Mayle 192*9712c20fSFrederick Mayle NSString* contentType = [self contentType]; 193*9712c20fSFrederick Mayle if ([contentType length] > 0) { 194*9712c20fSFrederick Mayle [req setValue:contentType forHTTPHeaderField:@"Content-type"]; 195*9712c20fSFrederick Mayle } 196*9712c20fSFrederick Mayle 197*9712c20fSFrederick Mayle NSData* bodyData = [self bodyData]; 198*9712c20fSFrederick Mayle if ([bodyData length] > 0) { 199*9712c20fSFrederick Mayle [req setHTTPBody:bodyData]; 200*9712c20fSFrederick Mayle } 201*9712c20fSFrederick Mayle 202*9712c20fSFrederick Mayle [req setHTTPMethod:[self HTTPMethod]]; 203*9712c20fSFrederick Mayle 204*9712c20fSFrederick Mayle [response_ release]; 205*9712c20fSFrederick Mayle response_ = nil; 206*9712c20fSFrederick Mayle 207*9712c20fSFrederick Mayle NSData* data = nil; 208*9712c20fSFrederick Mayle if ([[req URL] isFileURL]) { 209*9712c20fSFrederick Mayle [[req HTTPBody] writeToURL:[req URL] options:0 error:withError]; 210*9712c20fSFrederick Mayle } else { 211*9712c20fSFrederick Mayle NSURLResponse* response = nil; 212*9712c20fSFrederick Mayle data = SendSynchronousNSURLRequest(req, &response, withError); 213*9712c20fSFrederick Mayle response_ = (NSHTTPURLResponse*)[response retain]; 214*9712c20fSFrederick Mayle } 215*9712c20fSFrederick Mayle [req release]; 216*9712c20fSFrederick Mayle 217*9712c20fSFrederick Mayle return data; 218*9712c20fSFrederick Mayle} 219*9712c20fSFrederick Mayle 220*9712c20fSFrederick Mayle//============================================================================= 221*9712c20fSFrederick Mayle+ (NSData*)formDataForFileContents:(NSData*)contents withName:(NSString*)name { 222*9712c20fSFrederick Mayle NSMutableData* data = [NSMutableData data]; 223*9712c20fSFrederick Mayle NSString* escaped = PercentEncodeNSString(name); 224*9712c20fSFrederick Mayle NSString* fmt = @"Content-Disposition: form-data; name=\"%@\"; " 225*9712c20fSFrederick Mayle "filename=\"minidump.dmp\"\r\nContent-Type: " 226*9712c20fSFrederick Mayle "application/octet-stream\r\n\r\n"; 227*9712c20fSFrederick Mayle NSString* pre = [NSString stringWithFormat:fmt, escaped]; 228*9712c20fSFrederick Mayle 229*9712c20fSFrederick Mayle [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; 230*9712c20fSFrederick Mayle [data appendData:contents]; 231*9712c20fSFrederick Mayle 232*9712c20fSFrederick Mayle return data; 233*9712c20fSFrederick Mayle} 234*9712c20fSFrederick Mayle 235*9712c20fSFrederick Mayle//============================================================================= 236*9712c20fSFrederick Mayle+ (NSData*)formDataForFile:(NSString*)file withName:(NSString*)name { 237*9712c20fSFrederick Mayle NSData* contents = [NSData dataWithContentsOfFile:file]; 238*9712c20fSFrederick Mayle 239*9712c20fSFrederick Mayle return [HTTPRequest formDataForFileContents:contents withName:name]; 240*9712c20fSFrederick Mayle} 241*9712c20fSFrederick Mayle 242*9712c20fSFrederick Mayle//============================================================================= 243*9712c20fSFrederick Mayle+ (NSData*)formDataForKey:(NSString*)key value:(NSString*)value { 244*9712c20fSFrederick Mayle NSString* escaped = PercentEncodeNSString(key); 245*9712c20fSFrederick Mayle NSString* fmt = @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; 246*9712c20fSFrederick Mayle NSString* form = [NSString stringWithFormat:fmt, escaped, value]; 247*9712c20fSFrederick Mayle 248*9712c20fSFrederick Mayle return [form dataUsingEncoding:NSUTF8StringEncoding]; 249*9712c20fSFrederick Mayle} 250*9712c20fSFrederick Mayle 251*9712c20fSFrederick Mayle//============================================================================= 252*9712c20fSFrederick Mayle+ (void)appendFileToBodyData:(NSMutableData*)data 253*9712c20fSFrederick Mayle withName:(NSString*)name 254*9712c20fSFrederick Mayle withFileOrData:(id)fileOrData { 255*9712c20fSFrederick Mayle NSData* fileData; 256*9712c20fSFrederick Mayle 257*9712c20fSFrederick Mayle // The object can be either the path to a file (NSString) or the contents 258*9712c20fSFrederick Mayle // of the file (NSData). 259*9712c20fSFrederick Mayle if ([fileOrData isKindOfClass:[NSData class]]) 260*9712c20fSFrederick Mayle fileData = [self formDataForFileContents:fileOrData withName:name]; 261*9712c20fSFrederick Mayle else 262*9712c20fSFrederick Mayle fileData = [HTTPRequest formDataForFile:fileOrData withName:name]; 263*9712c20fSFrederick Mayle 264*9712c20fSFrederick Mayle [data appendData:fileData]; 265*9712c20fSFrederick Mayle} 266*9712c20fSFrederick Mayle 267*9712c20fSFrederick Mayle@end 268