diff --git a/src/common/mac/HTTPMultipartUpload.h b/src/common/mac/HTTPMultipartUpload.h index 42e8fed3..5eaaa91b 100644 --- a/src/common/mac/HTTPMultipartUpload.h +++ b/src/common/mac/HTTPMultipartUpload.h @@ -27,35 +27,39 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// HTTPMultipartUpload: A multipart/form-data HTTP uploader. -// Each parameter pair is sent as a boundary -// Each file is sent with a name field in addition to the filename and data -// The data will be sent synchronously. + #import -@interface HTTPMultipartUpload : NSObject { +#import "HTTPRequest.h" + /** + Represents a multipart/form-data HTTP upload (POST request). + Each parameter pair is sent as a boundary. + Each file is sent with a name field in addition to the filename and data. + */ +@interface HTTPMultipartUpload : HTTPRequest { @protected - NSURL *url_; // The destination URL (STRONG) NSDictionary *parameters_; // The key/value pairs for sending data (STRONG) NSMutableDictionary *files_; // Dictionary of name/file-path (STRONG) NSString *boundary_; // The boundary string (STRONG) - NSHTTPURLResponse *response_; // The response from the send (STRONG) } -- (id)initWithURL:(NSURL *)url; - -- (NSURL *)URL; - +/** + Sets the parameters that will be sent in the multipart POST request. + */ - (void)setParameters:(NSDictionary *)parameters; - (NSDictionary *)parameters; +/** + Adds a file to be uploaded in the multipart POST request, by its file path. + */ - (void)addFileAtPath:(NSString *)path name:(NSString *)name; + +/** + Adds a file to be uploaded in the multipart POST request, by its name and + contents. + */ - (void)addFileContents:(NSData *)data name:(NSString *)name; - (NSDictionary *)files; -// Set the data and return the response -- (NSData *)send:(NSError **)error; -- (NSHTTPURLResponse *)response; - @end diff --git a/src/common/mac/HTTPMultipartUpload.m b/src/common/mac/HTTPMultipartUpload.m index a3677f25..7fc474b8 100644 --- a/src/common/mac/HTTPMultipartUpload.m +++ b/src/common/mac/HTTPMultipartUpload.m @@ -28,67 +28,10 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import "HTTPMultipartUpload.h" + #import "GTMDefines.h" +#import "util.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, @@ -111,33 +54,24 @@ static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, //============================================================================= - (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { + NSMutableData* data = [NSMutableData data]; + [self appendBoundaryData:data]; + NSString *escaped = PercentEncodeNSString(key); NSString *fmt = - @"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; + @"Content-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]; - + [data appendData:[form dataUsingEncoding:NSUTF8StringEncoding]]; return data; } //============================================================================= -- (NSData *)formDataForFile:(NSString *)file name:(NSString *)name { - NSData *contents = [NSData dataWithContentsOfFile:file]; +- (void)appendBoundaryData: (NSMutableData*)data { + NSString *fmt = @"--%@\r\n"; + NSString *pre = [NSString stringWithFormat:fmt, boundary_]; - return [self formDataForFileContents:contents name:name]; + [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; } //============================================================================= @@ -145,8 +79,7 @@ static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, #pragma mark || Public || //============================================================================= - (id)initWithURL:(NSURL *)url { - if ((self = [super init])) { - url_ = [url copy]; + if ((self = [super initWithURL:url])) { boundary_ = [[self multipartBoundary] retain]; files_ = [[NSMutableDictionary alloc] init]; } @@ -156,20 +89,13 @@ static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, //============================================================================= - (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_) { @@ -199,17 +125,20 @@ static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, } //============================================================================= -- (NSData *)send:(NSError **)error { - NSMutableURLRequest *req = - [[NSMutableURLRequest alloc] - initWithURL:url_ cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:60.0]; +- (NSString*)HTTPMethod { + return @"POST"; +} +//============================================================================= +- (NSString*)contentType { + return [NSString stringWithFormat:@"multipart/form-data; boundary=%@", + boundary_]; +} + +//============================================================================= +- (NSData*)bodyData { 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; @@ -224,44 +153,19 @@ static NSData *SendSynchronousNSURLRequest(NSURLRequest *req, // Add any files to the message NSArray *fileNames = [files_ allKeys]; for (NSString *name in fileNames) { + // First append boundary + [self appendBoundaryData:postBody]; + // Then the formdata 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]; + [HTTPRequest appendFileToBodyData:postBody + withName:name + withFileOrData:fileOrData]; } 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_; + return postBody; } @end diff --git a/src/tools/mac/symupload/HTTPGetRequest.h b/src/tools/mac/symupload/HTTPGetRequest.h new file mode 100644 index 00000000..0da08474 --- /dev/null +++ b/src/tools/mac/symupload/HTTPGetRequest.h @@ -0,0 +1,42 @@ +// Copyright (c) 2019, 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 + +#import "HTTPRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Represents a HTTP GET request + */ +@interface HTTPGetRequest : HTTPRequest +@end + +NS_ASSUME_NONNULL_END diff --git a/src/tools/mac/symupload/HTTPGetRequest.m b/src/tools/mac/symupload/HTTPGetRequest.m new file mode 100644 index 00000000..a3a252ab --- /dev/null +++ b/src/tools/mac/symupload/HTTPGetRequest.m @@ -0,0 +1,39 @@ +// Copyright (c) 2019, 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 "HTTPGetRequest.h" + +@implementation HTTPGetRequest + +//============================================================================= +- (NSString*)HTTPMethod { + return @"GET"; +} + +@end diff --git a/src/tools/mac/symupload/HTTPPutRequest.h b/src/tools/mac/symupload/HTTPPutRequest.h new file mode 100644 index 00000000..f316212b --- /dev/null +++ b/src/tools/mac/symupload/HTTPPutRequest.h @@ -0,0 +1,51 @@ +// Copyright (c) 2019, 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 + +#import "HTTPRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Represents an HTTP PUT request. + */ +@interface HTTPPutRequest : HTTPRequest { +@protected + NSString* file_; +} + +/** + Sets the path of the file that will be sent in the PUT request. + */ +- (void)setFile:(NSString*)file; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/tools/mac/symupload/HTTPPutRequest.m b/src/tools/mac/symupload/HTTPPutRequest.m new file mode 100644 index 00000000..b64caae5 --- /dev/null +++ b/src/tools/mac/symupload/HTTPPutRequest.m @@ -0,0 +1,62 @@ +// Copyright (c) 2019, 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 "HTTPPutRequest.h" + +@implementation HTTPPutRequest + +//============================================================================= +- (void)dealloc { + [file_ release]; + + [super dealloc]; +} + +//============================================================================= +- (void)setFile:(NSString *)file { + file_ = [file copy]; +} + +//============================================================================= +- (NSString*)HTTPMethod { + return @"PUT"; +} + +//============================================================================= +- (NSData*)bodyData { + NSMutableData *postBody = [NSMutableData data]; + + [HTTPRequest appendFileToBodyData:postBody + withName:@"symbol_file" + withFileOrData:file_]; + + return postBody; +} + +@end diff --git a/src/tools/mac/symupload/HTTPRequest.h b/src/tools/mac/symupload/HTTPRequest.h new file mode 100644 index 00000000..ef2f86bc --- /dev/null +++ b/src/tools/mac/symupload/HTTPRequest.h @@ -0,0 +1,73 @@ +// Copyright (c) 2019, 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 + +NS_ASSUME_NONNULL_BEGIN +/** + Represents a single HTTP request. Sending the request is synchronous. + Once the send is complete, the response will be set. + + This is a base interface that specific HTTP requests derive from. + It is not intended to be instantiated directly. + */ +@interface HTTPRequest : NSObject { + @protected + NSURL *URL_; // The destination URL (STRONG) + NSHTTPURLResponse *response_; // The response from the send (STRONG) +} + +/** + Initializes the HTTPRequest and sets its URL. + */ +- (id)initWithURL:(NSURL *)URL; + +- (NSURL *)URL; + +- (NSHTTPURLResponse*) response; + +- (NSString*)HTTPMethod; // Internal, don't call outside class hierarchy. + +- (NSString*)contentType; // Internal, don't call outside class hierarchy. + +- (NSData*)bodyData; // Internal, don't call outside class hierarchy. + +- (NSData *)send:(NSError **)error; + +/** + Appends a file to the HTTP request, either by filename or by file content + (in the form of NSData). + */ ++ (void)appendFileToBodyData:(NSMutableData *)data + withName:(NSString*)name + withFileOrData:(id)fileOrData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/tools/mac/symupload/HTTPRequest.m b/src/tools/mac/symupload/HTTPRequest.m new file mode 100644 index 00000000..9cafe13e --- /dev/null +++ b/src/tools/mac/symupload/HTTPRequest.m @@ -0,0 +1,214 @@ +// Copyright (c) 2019, 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 "HTTPRequest.h" + +#import "util.h" + +// 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 **outResponse, + NSError **outError) { +#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 waitSemaphone = dispatch_semaphore_create(0); + NSURLSessionConfiguration* config = [NSURLSessionConfiguration + defaultSessionConfiguration]; + [config setTimeoutIntervalForRequest:240.0]; + NSURLSession* session = [NSURLSession sessionWithConfiguration:config]; + [[session + dataTaskWithRequest:req + completionHandler:^(NSData *data, + NSURLResponse *resp, + NSError *err) { + if (outError) + error = [err retain]; + if (outResponse) + response = [resp retain]; + if (err == nil) + result = [data retain]; + dispatch_semaphore_signal(waitSemaphone); + }] resume]; + dispatch_semaphore_wait(waitSemaphone, DISPATCH_TIME_FOREVER); + dispatch_release(waitSemaphone); + if (outError) + *outError = [error autorelease]; + if (outResponse) + *outResponse = [response autorelease]; + return [result autorelease]; +#else + return [NSURLConnection sendSynchronousRequest:req + returningResponse:outResponse + error:outError]; +#endif +} + +@implementation HTTPRequest + +//============================================================================= +- (id)initWithURL:(NSURL *)URL { + if ((self = [super init])) { + URL_ = [URL copy]; + } + + return self; +} + +//============================================================================= +- (void)dealloc { + [URL_ release]; + [response_ release]; + + [super dealloc]; +} + +//============================================================================= +- (NSURL *)URL { + return URL_; +} + +//============================================================================= +- (NSHTTPURLResponse *)response { + return response_; +} + +//============================================================================= +- (NSString*)HTTPMethod { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must" + "override %@ in a subclass", + NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +//============================================================================= +- (NSString*)contentType { + return nil; +} + +//============================================================================= +- (NSData*)bodyData { + return nil; +} + +//============================================================================= +- (NSData *)send:(NSError **)withError { + NSMutableURLRequest *req = + [[NSMutableURLRequest alloc] + initWithURL:URL_ + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:60.0]; + + NSString* contentType = [self contentType]; + if ([contentType length] > 0) { + [req setValue:contentType forHTTPHeaderField:@"Content-type"]; + } + + NSData* bodyData = [self bodyData]; + if ([bodyData length] > 0) { + [req setHTTPBody:bodyData]; + } + + [req setHTTPMethod:[self HTTPMethod]]; + + [response_ release]; + response_ = nil; + + NSData *data = nil; + if ([[req URL] isFileURL]) { + [[req HTTPBody] writeToURL:[req URL] options:0 error:withError]; + } else { + NSURLResponse *response = nil; + data = SendSynchronousNSURLRequest(req, &response, withError); + response_ = (NSHTTPURLResponse *)[response retain]; + } + [req release]; + + return data; +} + +//============================================================================= ++ (NSData *)formDataForFileContents:(NSData *)contents + withName:(NSString *)name { + NSMutableData *data = [NSMutableData data]; + NSString *escaped = PercentEncodeNSString(name); + NSString *fmt = @"Content-Disposition: form-data; name=\"%@\"; " + "filename=\"minidump.dmp\"\r\nContent-Type: " + "application/octet-stream\r\n\r\n"; + NSString *pre = [NSString stringWithFormat:fmt, escaped]; + + [data appendData:[pre dataUsingEncoding:NSUTF8StringEncoding]]; + [data appendData:contents]; + + return data; +} + +//============================================================================= ++ (NSData *)formDataForFile:(NSString *)file withName:(NSString *)name { + NSData *contents = [NSData dataWithContentsOfFile:file]; + + return [HTTPRequest formDataForFileContents:contents withName:name]; +} + +//============================================================================= ++ (NSData *)formDataForKey:(NSString *)key value:(NSString *)value { + NSString *escaped = PercentEncodeNSString(key); + NSString *fmt = + @"Content-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n"; + NSString *form = [NSString stringWithFormat:fmt, escaped, value]; + + return [form dataUsingEncoding:NSUTF8StringEncoding]; +} + +//============================================================================= ++ (void)appendFileToBodyData:(NSMutableData *)data + withName:(NSString*)name + withFileOrData:(id)fileOrData { + 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 withName:name]; + else + fileData = [HTTPRequest formDataForFile:fileOrData withName:name]; + + [data appendData:fileData]; +} + +@end diff --git a/src/tools/mac/symupload/HTTPSimplePostRequest.h b/src/tools/mac/symupload/HTTPSimplePostRequest.h new file mode 100644 index 00000000..4b542b45 --- /dev/null +++ b/src/tools/mac/symupload/HTTPSimplePostRequest.h @@ -0,0 +1,57 @@ +// Copyright (c) 2019, 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 + +#import "HTTPRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Represents a simple (non-multipart) HTTP POST request. + */ +@interface HTTPSimplePostRequest : HTTPRequest { +@protected + NSString* contentType_; + NSString* body_; +} + +/** + Sets the content type of the POST request. + */ +- (void)setContentType:(NSString*)contentType; + +/** + Sets the contents of the POST request's body. + */ +- (void)setBody:(NSString*)body; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/tools/mac/symupload/HTTPSimplePostRequest.m b/src/tools/mac/symupload/HTTPSimplePostRequest.m new file mode 100644 index 00000000..ef5ef334 --- /dev/null +++ b/src/tools/mac/symupload/HTTPSimplePostRequest.m @@ -0,0 +1,69 @@ +// Copyright (c) 2019, 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 "HTTPSimplePostRequest.h" + +@implementation HTTPSimplePostRequest + +//============================================================================= +- (void)dealloc { + [contentType_ release]; + [body_ release]; + + [super dealloc]; +} + +//============================================================================= +- (void)setContentType:(NSString *)contentType { + contentType_ = [contentType copy]; +} + +//============================================================================= +- (void)setBody:(NSString *)body { + body_ = [body copy]; +} + +//============================================================================= +- (NSString*)HTTPMethod { + return @"POST"; +} + +//============================================================================= +- (NSString*)contentType { + return contentType_; +} + +//============================================================================= +- (NSData*)bodyData { + NSMutableData* data = [NSMutableData data]; + [data appendData:[body_ dataUsingEncoding:NSUTF8StringEncoding]]; + return data; +} + +@end diff --git a/src/tools/mac/symupload/SymbolCollectorClient.h b/src/tools/mac/symupload/SymbolCollectorClient.h new file mode 100644 index 00000000..ef2029e4 --- /dev/null +++ b/src/tools/mac/symupload/SymbolCollectorClient.h @@ -0,0 +1,100 @@ +// Copyright (c) 2019, 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + Represents a response from a sym-upload-v2 server to a CreateUploadURL call. + */ +@interface UploadURLResponse : NSObject { +@protected + NSString* uploadURL_; + NSString* uploadKey_; +} + +- (id)initWithUploadURL:(NSString*)uploadURL + withUploadKey:(NSString*)uploadKey; + +- (NSString*)uploadURL; +- (NSString*)uploadKey; +@end + +/** + Possible return statuses from a sym-upload-v2 server to a CompleteUpload call. + */ +typedef NS_ENUM(NSInteger, CompleteUploadResult) { + CompleteUploadResultOk, + CompleteUploadResultDuplicateData, + CompleteUploadResultError +}; + +/** + Possible return statuses from a sym-upload-v2 server to a CheckSymbolStatus + call. + */ +typedef NS_ENUM(NSInteger, SymbolStatus) { + SymbolStatusFound, + SymbolStatusMissing, + SymbolStatusUnknown +}; + +/** + Interface to help a client interact with a sym-upload-v2 server, over HTTP. + For details of the API and protocol, see :/docs/sym_upload_v2_protocol.md. + */ +@interface SymbolCollectorClient : NSObject; + +/** + Call the CheckSymbolstatus API on the server. + */ ++ (SymbolStatus)CheckSymbolStatus:(NSString*)APIURL + withAPIKey:(NSString*)APIKey + withDebugFile:(NSString*)debugFile + withDebugID:(NSString*)debugID; + +/** + Call the CreateUploadURL API on the server. + */ ++ (UploadURLResponse*)CreateUploadURL:(NSString*)APIURL + withAPIKey:(NSString*)APIKey; + +/** + Call the CompleteUpload API on the server. + */ ++ (CompleteUploadResult)CompleteUpload:(NSString*)APIURL + withAPIKey:(NSString*)APIKey + withUploadKey:(NSString*)uploadKey + withDebugFile:(NSString*)debugFile + withDebugID:(NSString*)debugID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/tools/mac/symupload/SymbolCollectorClient.m b/src/tools/mac/symupload/SymbolCollectorClient.m new file mode 100644 index 00000000..38103ccb --- /dev/null +++ b/src/tools/mac/symupload/SymbolCollectorClient.m @@ -0,0 +1,247 @@ +// Copyright (c) 2019, 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 "SymbolCollectorClient.h" + +#import "HTTPGetRequest.h" +#import "HTTPSimplePostRequest.h" + +@implementation UploadURLResponse + +//============================================================================= +- (id)initWithUploadURL:(NSString *)uploadURL + withUploadKey:(NSString *)uploadKey { + if (self = [super init]) { + uploadURL_ = [uploadURL copy]; + uploadKey_ = [uploadKey copy]; + } + return self; +} + +//============================================================================= +- (void)dealloc { + [uploadURL_ release]; + [uploadKey_ release]; + + [super dealloc]; +} + +//============================================================================= +- (NSString*)uploadURL { + return uploadURL_; +} + +//============================================================================= +- (NSString*)uploadKey { + return uploadKey_; +} +@end + +@implementation SymbolCollectorClient + +//============================================================================= ++ (SymbolStatus)CheckSymbolStatus:(NSString *)APIURL + withAPIKey:(NSString *)APIKey + withDebugFile:(NSString *)debugFile + withDebugID:(NSString *)debugID { + NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat: + @"%@/v1/symbols/%@/%@:checkStatus" + @"?key=%@", + APIURL, + debugFile, + debugID, + APIKey]]; + + HTTPGetRequest* getRequest = [[HTTPGetRequest alloc] initWithURL:URL]; + NSError *error = nil; + NSData *data = [getRequest send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + int responseCode = [[getRequest response] statusCode]; + [getRequest release]; + + if (error || responseCode != 200) { + fprintf(stdout, "Failed to check symbol status.\n"); + fprintf(stdout, "Response code: %d\n", responseCode); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return SymbolStatusUnknown; + } + + error = nil; + NSRegularExpression* statusRegex = [NSRegularExpression + regularExpressionWithPattern: + @"\"status\": \"([^\"]+)\"" + options:0 + error:&error]; + NSArray* matches = [statusRegex + matchesInString:result + options:0 + range: NSMakeRange(0, [result length])]; + if ([matches count] != 1) { + fprintf(stdout, "Failed to parse check symbol status response."); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return SymbolStatusUnknown; + } + + NSString* status = [result substringWithRange:[matches[0] rangeAtIndex:1]]; + [result release]; + + return [status isEqualToString:@"FOUND"] ? + SymbolStatusFound : + SymbolStatusMissing; +} + +//============================================================================= ++ (UploadURLResponse *)CreateUploadURL:(NSString *)APIURL + withAPIKey:(NSString *)APIKey { + NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat: + @"%@/v1/uploads:create?key=%@", + APIURL, + APIKey]]; + + HTTPSimplePostRequest* postRequest = [[HTTPSimplePostRequest alloc] + initWithURL:URL]; + NSError *error = nil; + NSData* data = [postRequest send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + int responseCode = [[postRequest response] statusCode]; + [postRequest release]; + + if (error || responseCode != 200) { + fprintf(stdout, "Failed to create upload URL.\n"); + fprintf(stdout, "Response code: %d\n", responseCode); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return nil; + } + + // Note camel-case rather than underscores. + NSRegularExpression* uploadURLRegex = [NSRegularExpression + regularExpressionWithPattern: + @"\"uploadUrl\": \"([^\"]+)\"" + options:0 + error:&error]; + NSRegularExpression* uploadKeyRegex = [NSRegularExpression + regularExpressionWithPattern: + @"\"uploadKey\": \"([^\"]+)\"" + options:0 + error:&error]; + + NSArray* uploadURLMatches = [uploadURLRegex + matchesInString:result + options:0 + range: NSMakeRange(0, [result length])]; + NSArray* uploadKeyMatches = [uploadKeyRegex + matchesInString:result + options:0 + range: NSMakeRange(0, [result length])]; + if ([uploadURLMatches count] != 1 || [uploadKeyMatches count] != 1) { + fprintf(stdout, "Failed to parse create url response."); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return nil; + } + NSString* uploadURL = [result + substringWithRange:[uploadURLMatches[0] + rangeAtIndex:1]]; + NSString* uploadKey = [result + substringWithRange:[uploadKeyMatches[0] + rangeAtIndex:1]]; + + return [[UploadURLResponse alloc] initWithUploadURL:uploadURL + withUploadKey:uploadKey]; +} + +//============================================================================= ++ (CompleteUploadResult)CompleteUpload:(NSString *)APIURL + withAPIKey:(NSString *)APIKey + withUploadKey:(NSString *)uploadKey + withDebugFile:(NSString *)debugFile + withDebugID:(NSString *)debugID { + NSURL* URL = [NSURL URLWithString:[NSString stringWithFormat: + @"%@/v1/uploads/%@:complete?key=%@", + APIURL, + uploadKey, + APIKey]]; + NSString* body = [NSString stringWithFormat: + @"{ symbol_id: { debug_file: \"%@\", debug_id: \"%@\" } }", + debugFile, + debugID]; + + HTTPSimplePostRequest* postRequest = [[HTTPSimplePostRequest alloc] + initWithURL:URL]; + [postRequest setBody:body]; + [postRequest setContentType:@"application/json"]; + + NSError *error = nil; + NSData* data = [postRequest send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + int responseCode = [[postRequest response] statusCode]; + [postRequest release]; + + if (error || responseCode != 200) { + fprintf(stdout, "Failed to complete upload URL.\n"); + fprintf(stdout, "Response code: %d\n", responseCode); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return CompleteUploadResultError; + } + + // Note camel-case rather than underscores. + NSRegularExpression* completeResultRegex = [NSRegularExpression + regularExpressionWithPattern: + @"\"result\": \"([^\"]+)\"" + options:0 + error:&error]; + + NSArray* completeResultMatches = [completeResultRegex + matchesInString:result + options:0 + range: NSMakeRange(0, [result length])]; + + if ([completeResultMatches count] != 1) { + fprintf(stdout, "Failed to parse complete upload response."); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return nil; + } + NSString* completeResult = [result + substringWithRange:[completeResultMatches[0] + rangeAtIndex:1]]; + [result release]; + + return ([completeResult isEqualToString:@"DUPLICATE_DATA"]) ? + CompleteUploadResultDuplicateData : + CompleteUploadResultOk; +} +@end diff --git a/src/tools/mac/symupload/symupload.m b/src/tools/mac/symupload/symupload.m index a7cce7b0..a87dd4ea 100644 --- a/src/tools/mac/symupload/symupload.m +++ b/src/tools/mac/symupload/symupload.m @@ -43,11 +43,22 @@ #include #include + #include "HTTPMultipartUpload.h" +#include "HTTPPutRequest.h" +#include "SymbolCollectorClient.h" + +typedef enum { + SymUploadProtocolV1, + SymUploadProtocolV2 +} SymUploadProtocol; typedef struct { NSString *symbolsPath; NSString *uploadURLStr; + SymUploadProtocol symUploadProtocol; + NSString* apiKey; + BOOL force; BOOL success; } Options; @@ -84,15 +95,12 @@ static NSArray *ModuleDataForSymbolFile(NSString *file) { } //============================================================================= -static void Start(Options *options) { +static void StartSymUploadProtocolV1(Options* options, + NSArray* moduleParts, + NSString* compactedID) { NSURL *url = [NSURL URLWithString:options->uploadURLStr]; HTTPMultipartUpload *ul = [[HTTPMultipartUpload alloc] initWithURL:url]; NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; - NSArray *moduleParts = ModuleDataForSymbolFile(options->symbolsPath); - NSMutableString *compactedID = - [NSMutableString stringWithString:[moduleParts objectAtIndex:3]]; - [compactedID replaceOccurrencesOfString:@"-" withString:@"" options:0 - range:NSMakeRange(0, [compactedID length])]; // Add parameters [parameters setObject:compactedID forKey:@"debug_identifier"]; @@ -135,13 +143,106 @@ static void Start(Options *options) { options->success = !error && status==200; } +//============================================================================= +static void StartSymUploadProtocolV2(Options* options, + NSString* debugFile, + NSString* debugID) { + if (!options->force) { + SymbolStatus symbolStatus = [SymbolCollectorClient + CheckSymbolStatus:options->uploadURLStr + withAPIKey:options->apiKey + withDebugFile:debugFile + withDebugID:debugID]; + if (symbolStatus == SymbolStatusFound) { + fprintf(stdout, "Symbol file already exists, upload aborted." + " Use \"-f\" to overwrite.\n"); + options->success = YES; + return; + } else if (symbolStatus == SymbolStatusUnknown) { + fprintf(stdout, "Failed to get check for existing symbol.\n"); + return; + } + } + + UploadURLResponse* URLResponse = [SymbolCollectorClient + CreateUploadURL:options->uploadURLStr + withAPIKey:options->apiKey]; + if (URLResponse == nil) { + return; + } + + NSURL* uploadURL = [NSURL URLWithString:[URLResponse uploadURL]]; + HTTPPutRequest* putRequest = [[HTTPPutRequest alloc] + initWithURL:uploadURL]; + [putRequest setFile:options->symbolsPath]; + + NSError *error = nil; + NSData* data = [putRequest send:&error]; + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + int responseCode = [[putRequest response] statusCode]; + [putRequest release]; + + if (error || responseCode != 200) { + fprintf(stdout, "Failed to upload symbol file.\n"); + fprintf(stdout, "Response code: %d\n", responseCode); + fprintf(stdout, "Response:\n"); + fprintf(stdout, "%s\n", [result UTF8String]); + return; + } + + CompleteUploadResult completeUploadResult = [SymbolCollectorClient + CompleteUpload:options->uploadURLStr + withAPIKey:options->apiKey + withUploadKey:[URLResponse uploadKey] + withDebugFile:debugFile + withDebugID:debugID]; + [URLResponse release]; + if (completeUploadResult == CompleteUploadResultError) { + fprintf(stdout, "Failed to complete upload.\n"); + return; + } else if (completeUploadResult == CompleteUploadResultDuplicateData) { + fprintf(stdout, "Uploaded file checksum matched existing file checksum," + " no change necessary.\n"); + } else { + fprintf(stdout, "Successfully sent the symbol file.\n"); + } + options->success = YES; +} + +//============================================================================= +static void Start(Options *options) { + NSArray *moduleParts = ModuleDataForSymbolFile(options->symbolsPath); + NSMutableString *compactedID = + [NSMutableString stringWithString:[moduleParts objectAtIndex:3]]; + [compactedID replaceOccurrencesOfString:@"-" withString:@"" options:0 + range:NSMakeRange(0, [compactedID length])]; + + if (options->symUploadProtocol == SymUploadProtocolV1) { + StartSymUploadProtocolV1(options, moduleParts, compactedID); + } else if (options->symUploadProtocol == SymUploadProtocolV2) { + StartSymUploadProtocolV2(options, + [moduleParts objectAtIndex:4], + compactedID); + } +} + //============================================================================= static void Usage(int argc, const char *argv[]) { fprintf(stderr, "Submit symbol information.\n"); - fprintf(stderr, "Usage: %s \n", argv[0]); - fprintf(stderr, " should be created by using the dump_syms tool.\n"); - fprintf(stderr, " is the destination for the upload\n"); + fprintf(stderr, "Usage: %s [options] \n", argv[0]); + fprintf(stderr, " should be created by using the dump_syms " + "tool.\n"); + fprintf(stderr, " is the destination for the upload.\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, "\t-p : protocol to use for upload, accepts " + "[\"sym-upload-v1\", \"sym-upload-v2\"]. Default is " + "\"sym-upload-v1\".\n"); + fprintf(stderr, "\t-k : secret for authentication with upload " + "server. [Only in sym-upload-v2 protocol mode]\n"); + fprintf(stderr, "\t-f: Overwrite symbol file on server if already present. " + "[Only in sym-upload-v2 protocol mode]\n"); fprintf(stderr, "\t-h: Usage\n"); fprintf(stderr, "\t-?: Usage\n"); } @@ -149,11 +250,33 @@ Usage(int argc, const char *argv[]) { //============================================================================= static void SetupOptions(int argc, const char *argv[], Options *options) { + // Set default value of symUploadProtocol. + options->symUploadProtocol = SymUploadProtocolV1; + extern int optind; char ch; - while ((ch = getopt(argc, (char * const *)argv, "h?")) != -1) { + while ((ch = getopt(argc, (char * const *)argv, "p:k:hf?")) != -1) { switch (ch) { + case 'p': + if (strcmp(optarg, "sym-upload-v2") == 0) { + options->symUploadProtocol = SymUploadProtocolV2; + break; + } else if (strcmp(optarg, "sym-upload-v1") == 0) { + // This is already the default but leave in case that changes. + options->symUploadProtocol = SymUploadProtocolV1; + break; + } + Usage(argc, argv); + exit(0); + break; + case 'k': + options->apiKey = [NSString stringWithCString:optarg + encoding:NSASCIIStringEncoding]; + break; + case 'f': + options->force = YES; + break; default: Usage(argc, argv); exit(0); diff --git a/src/tools/mac/symupload/symupload.xcodeproj/project.pbxproj b/src/tools/mac/symupload/symupload.xcodeproj/project.pbxproj index a6a78dc5..edf7b1a0 100644 --- a/src/tools/mac/symupload/symupload.xcodeproj/project.pbxproj +++ b/src/tools/mac/symupload/symupload.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 5B6060BD222716FC0015F0A0 /* HTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6060BC222716FC0015F0A0 /* HTTPRequest.m */; }; + 5B6060C02227201B0015F0A0 /* HTTPPutRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6060BF2227201B0015F0A0 /* HTTPPutRequest.m */; }; + 5B6060C7222735E50015F0A0 /* HTTPGetRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6060C6222735E50015F0A0 /* HTTPGetRequest.m */; }; + 5B6060CA2227374E0015F0A0 /* HTTPSimplePostRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6060C92227374E0015F0A0 /* HTTPSimplePostRequest.m */; }; + 5B6060D022273BDA0015F0A0 /* SymbolCollectorClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B6060CF22273BDA0015F0A0 /* SymbolCollectorClient.m */; }; 8B31022C11F0CEBD00FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; 8DD76F9A0486AA7600D96B5E /* symupload.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* symupload.m */; settings = {ATTRIBUTES = (); }; }; 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; @@ -30,14 +35,25 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 08FB7796FE84155DC02AAC07 /* symupload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = symupload.m; sourceTree = ""; }; + 08FB7796FE84155DC02AAC07 /* symupload.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = symupload.m; sourceTree = ""; tabWidth = 2; }; 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 5B6060BB222716FC0015F0A0 /* HTTPRequest.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = ""; tabWidth = 2; }; + 5B6060BC222716FC0015F0A0 /* HTTPRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPRequest.m; sourceTree = ""; tabWidth = 2; }; + 5B6060BE2227201B0015F0A0 /* HTTPPutRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPPutRequest.h; sourceTree = ""; }; + 5B6060BF2227201B0015F0A0 /* HTTPPutRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPPutRequest.m; sourceTree = ""; tabWidth = 2; }; + 5B6060C22227303A0015F0A0 /* util.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = ""; tabWidth = 2; }; + 5B6060C5222735E50015F0A0 /* HTTPGetRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPGetRequest.h; sourceTree = ""; }; + 5B6060C6222735E50015F0A0 /* HTTPGetRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTTPGetRequest.m; sourceTree = ""; }; + 5B6060C82227374E0015F0A0 /* HTTPSimplePostRequest.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = HTTPSimplePostRequest.h; sourceTree = ""; tabWidth = 2; }; + 5B6060C92227374E0015F0A0 /* HTTPSimplePostRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPSimplePostRequest.m; sourceTree = ""; tabWidth = 2; }; + 5B6060CE22273BDA0015F0A0 /* SymbolCollectorClient.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = SymbolCollectorClient.h; sourceTree = ""; tabWidth = 2; }; + 5B6060CF22273BDA0015F0A0 /* SymbolCollectorClient.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = SymbolCollectorClient.m; sourceTree = ""; tabWidth = 2; }; 8B31022B11F0CE6900FCF3E4 /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; }; 8B3102B611F0D5CE00FCF3E4 /* BreakpadDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadDebug.xcconfig; path = ../../../common/mac/BreakpadDebug.xcconfig; sourceTree = SOURCE_ROOT; }; 8B3102B711F0D5CE00FCF3E4 /* BreakpadRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadRelease.xcconfig; path = ../../../common/mac/BreakpadRelease.xcconfig; sourceTree = SOURCE_ROOT; }; 8DD76FA10486AA7600D96B5E /* symupload */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = symupload; sourceTree = BUILT_PRODUCTS_DIR; }; - 9BD833680B03E4080055103E /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPMultipartUpload.h; path = ../../../common/mac/HTTPMultipartUpload.h; sourceTree = ""; }; - 9BD833690B03E4080055103E /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPMultipartUpload.m; path = ../../../common/mac/HTTPMultipartUpload.m; sourceTree = ""; }; + 9BD833680B03E4080055103E /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = HTTPMultipartUpload.h; path = ../../../common/mac/HTTPMultipartUpload.h; sourceTree = ""; tabWidth = 2; }; + 9BD833690B03E4080055103E /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; name = HTTPMultipartUpload.m; path = ../../../common/mac/HTTPMultipartUpload.m; sourceTree = ""; tabWidth = 2; }; 9BD835FB0B0544950055103E /* minidump_upload */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = minidump_upload; sourceTree = BUILT_PRODUCTS_DIR; }; 9BD836000B0544BA0055103E /* minidump_upload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = minidump_upload.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -65,6 +81,17 @@ 08FB7794FE84155DC02AAC07 /* symupload */ = { isa = PBXGroup; children = ( + 5B6060CE22273BDA0015F0A0 /* SymbolCollectorClient.h */, + 5B6060CF22273BDA0015F0A0 /* SymbolCollectorClient.m */, + 5B6060C82227374E0015F0A0 /* HTTPSimplePostRequest.h */, + 5B6060C92227374E0015F0A0 /* HTTPSimplePostRequest.m */, + 5B6060C5222735E50015F0A0 /* HTTPGetRequest.h */, + 5B6060C6222735E50015F0A0 /* HTTPGetRequest.m */, + 5B6060C22227303A0015F0A0 /* util.h */, + 5B6060BE2227201B0015F0A0 /* HTTPPutRequest.h */, + 5B6060BF2227201B0015F0A0 /* HTTPPutRequest.m */, + 5B6060BB222716FC0015F0A0 /* HTTPRequest.h */, + 5B6060BC222716FC0015F0A0 /* HTTPRequest.m */, 8B31022B11F0CE6900FCF3E4 /* Breakpad.xcconfig */, 8B3102B611F0D5CE00FCF3E4 /* BreakpadDebug.xcconfig */, 8B3102B711F0D5CE00FCF3E4 /* BreakpadRelease.xcconfig */, @@ -137,9 +164,15 @@ /* Begin PBXProject section */ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; + attributes = { + }; buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "symupload" */; compatibilityVersion = "Xcode 3.1"; + developmentRegion = en; hasScannedForEncodings = 1; + knownRegions = ( + en, + ); mainGroup = 08FB7794FE84155DC02AAC07 /* symupload */; projectDirPath = ""; projectRoot = ""; @@ -156,6 +189,11 @@ buildActionMask = 2147483647; files = ( 8DD76F9A0486AA7600D96B5E /* symupload.m in Sources */, + 5B6060CA2227374E0015F0A0 /* HTTPSimplePostRequest.m in Sources */, + 5B6060D022273BDA0015F0A0 /* SymbolCollectorClient.m in Sources */, + 5B6060C7222735E50015F0A0 /* HTTPGetRequest.m in Sources */, + 5B6060C02227201B0015F0A0 /* HTTPPutRequest.m in Sources */, + 5B6060BD222716FC0015F0A0 /* HTTPRequest.m in Sources */, 9BD8336B0B03E4080055103E /* HTTPMultipartUpload.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/src/tools/mac/symupload/util.h b/src/tools/mac/symupload/util.h new file mode 100644 index 00000000..67112868 --- /dev/null +++ b/src/tools/mac/symupload/util.h @@ -0,0 +1,52 @@ +// Copyright (c) 2019, 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. + +#ifndef util_h +#define util_h + +#import + +// 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 +} + +#endif /* util_h */