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