Port new symbol API to symupload on Mac.

- See documentation in Linux implementation commit: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1422400.

Change-Id: If3ff256e63f2db3ac9c0be78cfc17754d532cb88
Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/1497653
Reviewed-by: Ivan Penkov <ivanpe@chromium.org>
diff --git a/src/common/mac/HTTPMultipartUpload.h b/src/common/mac/HTTPMultipartUpload.h
index 42e8fed..5eaaa91 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 <Foundation/Foundation.h>
 
-@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 a3677f2..7fc474b 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 @@
 
 //=============================================================================
 - (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 @@
 #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,21 +89,14 @@
 
 //=============================================================================
 - (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];
@@ -199,17 +125,20 @@
 }
 
 //=============================================================================
-- (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 @@
   // 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 0000000..0da0847
--- /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 <Foundation/Foundation.h>
+
+#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 0000000..a3a252a
--- /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 0000000..f316212
--- /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 <Foundation/Foundation.h>
+
+#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 0000000..b64caae
--- /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 0000000..ef2f86b
--- /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 <Foundation/Foundation.h>
+
+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 0000000..9cafe13
--- /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 0000000..4b542b4
--- /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 <Foundation/Foundation.h>
+
+#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 0000000..ef5ef33
--- /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 0000000..ef2029e
--- /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 <Foundation/Foundation.h>
+
+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 0000000..38103cc
--- /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 a7cce7b..a87dd4e 100644
--- a/src/tools/mac/symupload/symupload.m
+++ b/src/tools/mac/symupload/symupload.m
@@ -43,11 +43,22 @@
 #include <unistd.h>
 
 #include <Foundation/Foundation.h>
+
 #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 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"];
@@ -136,12 +144,105 @@
 }
 
 //=============================================================================
+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 <symbols> <upload-URL>\n", argv[0]);
-  fprintf(stderr, "<symbols> should be created by using the dump_syms tool.\n");
-  fprintf(stderr, "<upload-URL> is the destination for the upload\n");
+  fprintf(stderr, "Usage: %s [options] <symbol-file> <upload-URL>\n", argv[0]);
+  fprintf(stderr, "<symbol-file> should be created by using the dump_syms "
+          "tool.\n");
+  fprintf(stderr, "<upload-URL> is the destination for the upload.\n");
+  fprintf(stderr, "Options:\n");
+  fprintf(stderr, "\t-p <protocol>: protocol to use for upload, accepts "
+          "[\"sym-upload-v1\", \"sym-upload-v2\"]. Default is "
+          "\"sym-upload-v1\".\n");
+  fprintf(stderr, "\t-k <api-key>: 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 @@
 //=============================================================================
 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 a6a78dc..edf7b1a 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 = "<group>"; };
+		08FB7796FE84155DC02AAC07 /* symupload.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = symupload.m; sourceTree = "<group>"; 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 = "<group>"; tabWidth = 2; };
+		5B6060BC222716FC0015F0A0 /* HTTPRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPRequest.m; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060BE2227201B0015F0A0 /* HTTPPutRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPPutRequest.h; sourceTree = "<group>"; };
+		5B6060BF2227201B0015F0A0 /* HTTPPutRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPPutRequest.m; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060C22227303A0015F0A0 /* util.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060C5222735E50015F0A0 /* HTTPGetRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPGetRequest.h; sourceTree = "<group>"; };
+		5B6060C6222735E50015F0A0 /* HTTPGetRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTTPGetRequest.m; sourceTree = "<group>"; };
+		5B6060C82227374E0015F0A0 /* HTTPSimplePostRequest.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = HTTPSimplePostRequest.h; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060C92227374E0015F0A0 /* HTTPSimplePostRequest.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = HTTPSimplePostRequest.m; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060CE22273BDA0015F0A0 /* SymbolCollectorClient.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = SymbolCollectorClient.h; sourceTree = "<group>"; tabWidth = 2; };
+		5B6060CF22273BDA0015F0A0 /* SymbolCollectorClient.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = SymbolCollectorClient.m; sourceTree = "<group>"; 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 = "<group>"; };
-		9BD833690B03E4080055103E /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPMultipartUpload.m; path = ../../../common/mac/HTTPMultipartUpload.m; sourceTree = "<group>"; };
+		9BD833680B03E4080055103E /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = HTTPMultipartUpload.h; path = ../../../common/mac/HTTPMultipartUpload.h; sourceTree = "<group>"; tabWidth = 2; };
+		9BD833690B03E4080055103E /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; name = HTTPMultipartUpload.m; path = ../../../common/mac/HTTPMultipartUpload.m; sourceTree = "<group>"; 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 = "<group>"; };
 /* 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 0000000..6711286
--- /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 <Foundation/Foundation.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
+}
+
+#endif /* util_h */