1// Copyright (c) 2020, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14//     * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#import "SymbolCollectorClient.h"
31
32#import "HTTPGetRequest.h"
33#import "HTTPSimplePostRequest.h"
34
35@implementation UploadURLResponse
36
37//=============================================================================
38- (id)initWithUploadURL:(NSString*)uploadURL
39          withUploadKey:(NSString*)uploadKey {
40  if (self = [super init]) {
41    uploadURL_ = [uploadURL copy];
42    uploadKey_ = [uploadKey copy];
43  }
44  return self;
45}
46
47//=============================================================================
48- (void)dealloc {
49  [uploadURL_ release];
50  [uploadKey_ release];
51
52  [super dealloc];
53}
54
55//=============================================================================
56- (NSString*)uploadURL {
57  return uploadURL_;
58}
59
60//=============================================================================
61- (NSString*)uploadKey {
62  return uploadKey_;
63}
64@end
65
66@implementation SymbolCollectorClient
67
68//=============================================================================
69+ (SymbolStatus)checkSymbolStatusOnServer:(NSString*)APIURL
70                               withAPIKey:(NSString*)APIKey
71                            withDebugFile:(NSString*)debugFile
72                              withDebugID:(NSString*)debugID {
73  // Note that forward-slash is listed as a character to escape here, for
74  // completeness, however it is illegal in a debugFile input.
75  NSMutableCharacterSet* allowedDebugFileCharacters = [NSMutableCharacterSet
76      characterSetWithCharactersInString:@" \"\\/#%:?@|^`{}<>[]&=;"];
77  [allowedDebugFileCharacters
78      formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
79  [allowedDebugFileCharacters invert];
80  NSString* escapedDebugFile =
81      [debugFile stringByAddingPercentEncodingWithAllowedCharacters:
82                     allowedDebugFileCharacters];
83
84  NSURL* URL = [NSURL
85      URLWithString:[NSString
86                        stringWithFormat:@"%@/v1/symbols/%@/%@:checkStatus"
87                                         @"?key=%@",
88                                         APIURL, escapedDebugFile, debugID,
89                                         APIKey]];
90
91  HTTPGetRequest* getRequest = [[HTTPGetRequest alloc] initWithURL:URL];
92  NSError* error = nil;
93  NSData* data = [getRequest send:&error];
94  NSString* result = [[NSString alloc] initWithData:data
95                                           encoding:NSUTF8StringEncoding];
96  int responseCode = [[getRequest response] statusCode];
97  [getRequest release];
98
99  if (error || responseCode != 200) {
100    fprintf(stdout, "Failed to check symbol status.\n");
101    fprintf(stdout, "Response code: %d\n", responseCode);
102    fprintf(stdout, "Response:\n");
103    fprintf(stdout, "%s\n", [result UTF8String]);
104    return SymbolStatusUnknown;
105  }
106
107  error = nil;
108  NSRegularExpression* statusRegex = [NSRegularExpression
109      regularExpressionWithPattern:@"\"status\": \"([^\"]+)\""
110                           options:0
111                             error:&error];
112  NSArray* matches =
113      [statusRegex matchesInString:result
114                           options:0
115                             range:NSMakeRange(0, [result length])];
116  if ([matches count] != 1) {
117    fprintf(stdout, "Failed to parse check symbol status response.");
118    fprintf(stdout, "Response:\n");
119    fprintf(stdout, "%s\n", [result UTF8String]);
120    return SymbolStatusUnknown;
121  }
122
123  NSString* status = [result substringWithRange:[matches[0] rangeAtIndex:1]];
124  [result release];
125
126  return [status isEqualToString:@"FOUND"] ? SymbolStatusFound
127                                           : SymbolStatusMissing;
128}
129
130//=============================================================================
131+ (UploadURLResponse*)createUploadURLOnServer:(NSString*)APIURL
132                                   withAPIKey:(NSString*)APIKey {
133  NSURL* URL = [NSURL
134      URLWithString:[NSString stringWithFormat:@"%@/v1/uploads:create?key=%@",
135                                               APIURL, APIKey]];
136
137  HTTPSimplePostRequest* postRequest =
138      [[HTTPSimplePostRequest alloc] initWithURL:URL];
139  NSError* error = nil;
140  NSData* data = [postRequest send:&error];
141  NSString* result = [[NSString alloc] initWithData:data
142                                           encoding:NSUTF8StringEncoding];
143  int responseCode = [[postRequest response] statusCode];
144  [postRequest release];
145
146  if (error || responseCode != 200) {
147    fprintf(stdout, "Failed to create upload URL.\n");
148    fprintf(stdout, "Response code: %d\n", responseCode);
149    fprintf(stdout, "Response:\n");
150    fprintf(stdout, "%s\n", [result UTF8String]);
151    return nil;
152  }
153
154  // Note camel-case rather than underscores.
155  NSRegularExpression* uploadURLRegex = [NSRegularExpression
156      regularExpressionWithPattern:@"\"uploadUrl\": \"([^\"]+)\""
157                           options:0
158                             error:&error];
159  NSRegularExpression* uploadKeyRegex = [NSRegularExpression
160      regularExpressionWithPattern:@"\"uploadKey\": \"([^\"]+)\""
161                           options:0
162                             error:&error];
163
164  NSArray* uploadURLMatches =
165      [uploadURLRegex matchesInString:result
166                              options:0
167                                range:NSMakeRange(0, [result length])];
168  NSArray* uploadKeyMatches =
169      [uploadKeyRegex matchesInString:result
170                              options:0
171                                range:NSMakeRange(0, [result length])];
172  if ([uploadURLMatches count] != 1 || [uploadKeyMatches count] != 1) {
173    fprintf(stdout, "Failed to parse create url response.");
174    fprintf(stdout, "Response:\n");
175    fprintf(stdout, "%s\n", [result UTF8String]);
176    return nil;
177  }
178  NSString* uploadURL =
179      [result substringWithRange:[uploadURLMatches[0] rangeAtIndex:1]];
180  NSString* uploadKey =
181      [result substringWithRange:[uploadKeyMatches[0] rangeAtIndex:1]];
182
183  return [[UploadURLResponse alloc] initWithUploadURL:uploadURL
184                                        withUploadKey:uploadKey];
185}
186
187//=============================================================================
188+ (CompleteUploadResult)completeUploadOnServer:(NSString*)APIURL
189                                    withAPIKey:(NSString*)APIKey
190                                 withUploadKey:(NSString*)uploadKey
191                                 withDebugFile:(NSString*)debugFile
192                                   withDebugID:(NSString*)debugID
193                                      withType:(NSString*)type {
194  NSURL* URL = [NSURL
195      URLWithString:[NSString
196                        stringWithFormat:@"%@/v1/uploads/%@:complete?key=%@",
197                                         APIURL, uploadKey, APIKey]];
198
199  NSDictionary* symbolIdDictionary =
200      [NSDictionary dictionaryWithObjectsAndKeys:debugFile, @"debug_file",
201                                                 debugID, @"debug_id", nil];
202  NSDictionary* jsonDictionary = [NSDictionary
203      dictionaryWithObjectsAndKeys:symbolIdDictionary, @"symbol_id", type,
204                                   @"symbol_upload_type", nil];
205  NSError* error = nil;
206  NSData* jsonData =
207      [NSJSONSerialization dataWithJSONObject:jsonDictionary
208                                      options:NSJSONWritingPrettyPrinted
209                                        error:&error];
210  if (jsonData == nil) {
211    fprintf(stdout, "Error: %s\n", [[error localizedDescription] UTF8String]);
212    fprintf(stdout,
213            "Failed to complete upload. Could not write JSON payload.\n");
214    return CompleteUploadResultError;
215  }
216
217  NSString* body = [[NSString alloc] initWithData:jsonData
218                                         encoding:NSUTF8StringEncoding];
219  HTTPSimplePostRequest* postRequest =
220      [[HTTPSimplePostRequest alloc] initWithURL:URL];
221  [postRequest setBody:body];
222  [postRequest setContentType:@"application/json"];
223
224  NSData* data = [postRequest send:&error];
225  if (data == nil) {
226    fprintf(stdout, "Error: %s\n", [[error localizedDescription] UTF8String]);
227    fprintf(stdout, "Failed to complete upload URL.\n");
228    return CompleteUploadResultError;
229  }
230
231  NSString* result = [[NSString alloc] initWithData:data
232                                           encoding:NSUTF8StringEncoding];
233  int responseCode = [[postRequest response] statusCode];
234  [postRequest release];
235  if (responseCode != 200) {
236    fprintf(stdout, "Failed to complete upload URL.\n");
237    fprintf(stdout, "Response code: %d\n", responseCode);
238    fprintf(stdout, "Response:\n");
239    fprintf(stdout, "%s\n", [result UTF8String]);
240    return CompleteUploadResultError;
241  }
242
243  // Note camel-case rather than underscores.
244  NSRegularExpression* completeResultRegex = [NSRegularExpression
245      regularExpressionWithPattern:@"\"result\": \"([^\"]+)\""
246                           options:0
247                             error:&error];
248
249  NSArray* completeResultMatches =
250      [completeResultRegex matchesInString:result
251                                   options:0
252                                     range:NSMakeRange(0, [result length])];
253
254  if ([completeResultMatches count] != 1) {
255    fprintf(stdout, "Failed to parse complete upload response.");
256    fprintf(stdout, "Response:\n");
257    fprintf(stdout, "%s\n", [result UTF8String]);
258    return CompleteUploadResultError;
259  }
260  NSString* completeResult =
261      [result substringWithRange:[completeResultMatches[0] rangeAtIndex:1]];
262  [result release];
263
264  return ([completeResult isEqualToString:@"DUPLICATE_DATA"])
265             ? CompleteUploadResultDuplicateData
266             : CompleteUploadResultOk;
267}
268@end
269