xref: /aosp_15_r20/external/google-breakpad/src/client/mac/sender/uploader.mm (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1// Copyright 2011 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 <fcntl.h>
30#include <stdio.h>
31#import <sys/stat.h>
32#include <TargetConditionals.h>
33#import <unistd.h>
34
35#import <SystemConfiguration/SystemConfiguration.h>
36
37#import "common/mac/HTTPMultipartUpload.h"
38
39#import "client/apple/Framework/BreakpadDefines.h"
40#import "client/mac/sender/uploader.h"
41
42const int kMinidumpFileLengthLimit = 2 * 1024 * 1024;  // 2MB
43
44#define kApplePrefsSyncExcludeAllKey \
45  @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
46
47NSString *const kGoogleServerType = @"google";
48NSString *const kSocorroServerType = @"socorro";
49NSString *const kDefaultServerType = @"google";
50
51#pragma mark -
52
53namespace {
54// Read one line from the configuration file.
55NSString *readString(int fileId) {
56  NSMutableString *str = [NSMutableString stringWithCapacity:32];
57  char ch[2] = { 0 };
58
59  while (read(fileId, &ch[0], 1) == 1) {
60    if (ch[0] == '\n') {
61      // Break if this is the first newline after reading some other string
62      // data.
63      if ([str length])
64        break;
65    } else {
66      [str appendString:[NSString stringWithUTF8String:ch]];
67    }
68  }
69
70  return str;
71}
72
73//=============================================================================
74// Read |length| of binary data from the configuration file. This method will
75// returns |nil| in case of error.
76NSData *readData(int fileId, ssize_t length) {
77  NSMutableData *data = [NSMutableData dataWithLength:length];
78  char *bytes = (char *)[data bytes];
79
80  if (read(fileId, bytes, length) != length)
81    return nil;
82
83  return data;
84}
85
86//=============================================================================
87// Read the configuration from the config file.
88NSDictionary *readConfigurationData(const char *configFile) {
89  int fileId = open(configFile, O_RDONLY, 0600);
90  if (fileId == -1) {
91    fprintf(stderr, "Breakpad Uploader: Couldn't open config file %s - %s",
92            configFile, strerror(errno));
93  }
94
95  // we want to avoid a build-up of old config files even if they
96  // have been incorrectly written by the framework
97  if (unlink(configFile)) {
98    fprintf(stderr, "Breakpad Uploader: Couldn't unlink config file %s - %s",
99            configFile, strerror(errno));
100  }
101
102  if (fileId == -1) {
103    return nil;
104  }
105
106  NSMutableDictionary *config = [NSMutableDictionary dictionary];
107
108  while (1) {
109    NSString *key = readString(fileId);
110
111    if (![key length])
112      break;
113
114    // Read the data.  Try to convert to a UTF-8 string, or just save
115    // the data
116    NSString *lenStr = readString(fileId);
117    ssize_t len = [lenStr intValue];
118    NSData *data = readData(fileId, len);
119    id value = [[NSString alloc] initWithData:data
120                                     encoding:NSUTF8StringEncoding];
121
122    [config setObject:(value ? value : data) forKey:key];
123    [value release];
124  }
125
126  close(fileId);
127  return config;
128}
129}  // namespace
130
131#pragma mark -
132
133@interface Uploader(PrivateMethods)
134
135// Update |parameters_| as well as the server parameters using |config|.
136- (void)translateConfigurationData:(NSDictionary *)config;
137
138// Read the minidump referenced in |parameters_| and update |minidumpContents_|
139// with its content.
140- (BOOL)readMinidumpData;
141
142// Read the log files referenced in |parameters_| and update |logFileData_|
143// with their content.
144- (BOOL)readLogFileData;
145
146// Returns a unique client id (user-specific), creating a persistent
147// one in the user defaults, if necessary.
148- (NSString*)clientID;
149
150// Returns a dictionary that can be used to map Breakpad parameter names to
151// URL parameter names.
152- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType;
153
154// Helper method to set HTTP parameters based on server type.  This is
155// called right before the upload - crashParameters will contain, on exit,
156// URL parameters that should be sent with the minidump.
157- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters;
158
159// Initialization helper to create dictionaries mapping Breakpad
160// parameters to URL parameters
161- (void)createServerParameterDictionaries;
162
163// Accessor method for the URL parameter dictionary
164- (NSMutableDictionary *)urlParameterDictionary;
165
166// Records the uploaded crash ID to the log file.
167- (void)logUploadWithID:(const char *)uploadID;
168
169// Builds an URL parameter for a given dictionary key. Uses Uploader's
170// parameters to provide its value. Returns nil if no item is stored for the
171// given key.
172- (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName
173                          forParamKey:(NSString *)key;
174@end
175
176@implementation Uploader
177
178//=============================================================================
179- (id)initWithConfigFile:(const char *)configFile {
180  NSDictionary *config = readConfigurationData(configFile);
181  if (!config)
182    return nil;
183
184  return [self initWithConfig:config];
185}
186
187//=============================================================================
188- (id)initWithConfig:(NSDictionary *)config {
189  if ((self = [super init])) {
190    // Because the reporter is embedded in the framework (and many copies
191    // of the framework may exist) its not completely certain that the OS
192    // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
193    // Info.plist. To make sure, also set the key directly if needed.
194    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
195    if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
196      [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
197    }
198
199    [self createServerParameterDictionaries];
200
201    [self translateConfigurationData:config];
202
203    // Read the minidump into memory.
204    [self readMinidumpData];
205    [self readLogFileData];
206  }
207  return self;
208}
209
210//=============================================================================
211+ (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile {
212  return readConfigurationData([configFile fileSystemRepresentation]);
213}
214
215//=============================================================================
216- (void)translateConfigurationData:(NSDictionary *)config {
217  parameters_ = [[NSMutableDictionary alloc] init];
218
219  NSEnumerator *it = [config keyEnumerator];
220  while (NSString *key = [it nextObject]) {
221    // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX
222    // that indicates that it should be uploaded to the server along
223    // with the minidump, so we treat it specially.
224    if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) {
225      NSString *urlParameterKey =
226        [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]];
227      if ([urlParameterKey length]) {
228        id value = [config objectForKey:key];
229        if ([value isKindOfClass:[NSString class]]) {
230          [self addServerParameter:(NSString *)value
231                            forKey:urlParameterKey];
232        } else {
233          [self addServerParameter:(NSData *)value
234                            forKey:urlParameterKey];
235        }
236      }
237    } else {
238      [parameters_ setObject:[config objectForKey:key] forKey:key];
239    }
240  }
241
242  // generate a unique client ID based on this host's MAC address
243  // then add a key/value pair for it
244  NSString *clientID = [self clientID];
245  [parameters_ setObject:clientID forKey:@"guid"];
246}
247
248// Per user per machine
249- (NSString *)clientID {
250  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
251  NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
252  if (crashClientID) {
253    return crashClientID;
254  }
255
256  // Otherwise, if we have no client id, generate one!
257  srandom((int)[[NSDate date] timeIntervalSince1970]);
258  long clientId1 = random();
259  long clientId2 = random();
260  long clientId3 = random();
261  crashClientID = [NSString stringWithFormat:@"%lx%lx%lx",
262                            clientId1, clientId2, clientId3];
263
264  [ud setObject:crashClientID forKey:kClientIdPreferenceKey];
265  [ud synchronize];
266  return crashClientID;
267}
268
269//=============================================================================
270- (BOOL)readLogFileData {
271#if TARGET_OS_IPHONE
272  return NO;
273#else
274  unsigned int logFileCounter = 0;
275
276  NSString *logPath;
277  size_t logFileTailSize =
278      [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue];
279
280  NSMutableArray *logFilenames; // An array of NSString, one per log file
281  logFilenames = [[NSMutableArray alloc] init];
282
283  char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
284  char *tmpDir = mkdtemp(tmpDirTemplate);
285
286  // Construct key names for the keys we expect to contain log file paths
287  for(logFileCounter = 0;; logFileCounter++) {
288    NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
289                                     @BREAKPAD_LOGFILE_KEY_PREFIX,
290                                     logFileCounter];
291
292    logPath = [parameters_ objectForKey:logFileKey];
293
294    // They should all be consecutive, so if we don't find one, assume
295    // we're done
296
297    if (!logPath) {
298      break;
299    }
300
301    NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
302
303    if (entireLogFile == nil) {
304      continue;
305    }
306
307    NSRange fileRange;
308
309    // Truncate the log file, only if necessary
310
311    if ([entireLogFile length] <= logFileTailSize) {
312      fileRange = NSMakeRange(0, [entireLogFile length]);
313    } else {
314      fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
315                              logFileTailSize);
316    }
317
318    char tmpFilenameTemplate[100];
319
320    // Generate a template based on the log filename
321    sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
322            [[logPath lastPathComponent] fileSystemRepresentation]);
323
324    char *tmpFile = mktemp(tmpFilenameTemplate);
325
326    NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
327    NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
328    [logSubdata writeToFile:tmpFileString atomically:NO];
329
330    [logFilenames addObject:[tmpFileString lastPathComponent]];
331    [entireLogFile release];
332  }
333
334  if ([logFilenames count] == 0) {
335    [logFilenames release];
336    logFileData_ =  nil;
337    return NO;
338  }
339
340  // now, bzip all files into one
341  NSTask *tarTask = [[NSTask alloc] init];
342
343  [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
344  [tarTask setLaunchPath:@"/usr/bin/tar"];
345
346  NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
347                                             @"log.tar.bz2",nil];
348  [bzipArgs addObjectsFromArray:logFilenames];
349
350  [logFilenames release];
351
352  [tarTask setArguments:bzipArgs];
353  [tarTask launch];
354  [tarTask waitUntilExit];
355  [tarTask release];
356
357  NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
358  logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
359  if (logFileData_ == nil) {
360    fprintf(stderr, "Breakpad Uploader: Cannot find temp tar log file: %s",
361            [logTarFile UTF8String]);
362    return NO;
363  }
364  return YES;
365#endif  // TARGET_OS_IPHONE
366}
367
368//=============================================================================
369- (BOOL)readMinidumpData {
370  NSString *minidumpDir =
371      [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
372  NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
373
374  if (![minidumpID length])
375    return NO;
376
377  NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
378  path = [path stringByAppendingPathExtension:@"dmp"];
379
380  // check the size of the minidump and limit it to a reasonable size
381  // before attempting to load into memory and upload
382  const char *fileName = [path fileSystemRepresentation];
383  struct stat fileStatus;
384
385  BOOL success = YES;
386
387  if (!stat(fileName, &fileStatus)) {
388    if (fileStatus.st_size > kMinidumpFileLengthLimit) {
389      fprintf(stderr, "Breakpad Uploader: minidump file too large " \
390              "to upload : %d\n", (int)fileStatus.st_size);
391      success = NO;
392    }
393  } else {
394      fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \
395              "file length\n");
396      success = NO;
397  }
398
399  if (success) {
400    minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
401    success = ([minidumpContents_ length] ? YES : NO);
402  }
403
404  if (!success) {
405    // something wrong with the minidump file -- delete it
406    unlink(fileName);
407  }
408
409  return success;
410}
411
412#pragma mark -
413//=============================================================================
414
415- (void)createServerParameterDictionaries {
416  serverDictionary_ = [[NSMutableDictionary alloc] init];
417  socorroDictionary_ = [[NSMutableDictionary alloc] init];
418  googleDictionary_ = [[NSMutableDictionary alloc] init];
419  extraServerVars_ = [[NSMutableDictionary alloc] init];
420
421  [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType];
422  [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType];
423
424  [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME];
425  [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL];
426  [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS];
427  [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT];
428  [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION];
429  [googleDictionary_ setObject:@"guid" forKey:@"guid"];
430
431  [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS];
432  [socorroDictionary_ setObject:@"CrashTime"
433                         forKey:@BREAKPAD_PROCESS_CRASH_TIME];
434  [socorroDictionary_ setObject:@"StartupTime"
435                         forKey:@BREAKPAD_PROCESS_START_TIME];
436  [socorroDictionary_ setObject:@"Version"
437                         forKey:@BREAKPAD_VERSION];
438  [socorroDictionary_ setObject:@"ProductName"
439                         forKey:@BREAKPAD_PRODUCT];
440  [socorroDictionary_ setObject:@"Email"
441                         forKey:@BREAKPAD_EMAIL];
442}
443
444- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType {
445  if (serverType == nil || [serverType length] == 0) {
446    return [serverDictionary_ objectForKey:kDefaultServerType];
447  }
448  return [serverDictionary_ objectForKey:serverType];
449}
450
451- (NSMutableDictionary *)urlParameterDictionary {
452  NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
453  return [self dictionaryForServerType:serverType];
454
455}
456
457- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters {
458  NSDictionary *urlParameterNames = [self urlParameterDictionary];
459
460  id key;
461  NSEnumerator *enumerator = [parameters_ keyEnumerator];
462
463  while ((key = [enumerator nextObject])) {
464    // The key from parameters_ corresponds to a key in
465    // urlParameterNames.  The value in parameters_ gets stored in
466    // crashParameters with a key that is the value in
467    // urlParameterNames.
468
469    // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and
470    // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP
471    // URL parameter becomes [pname => "FOOBAR"].
472    NSString *breakpadParameterName = (NSString *)key;
473    NSString *urlParameter = [urlParameterNames
474                                   objectForKey:breakpadParameterName];
475    if (urlParameter) {
476      [crashParameters setObject:[parameters_ objectForKey:key]
477                          forKey:urlParameter];
478    }
479  }
480
481  // Now, add the parameters that were added by the application.
482  enumerator = [extraServerVars_ keyEnumerator];
483
484  while ((key = [enumerator nextObject])) {
485    NSString *urlParameterName = (NSString *)key;
486    NSString *urlParameterValue =
487      [extraServerVars_ objectForKey:urlParameterName];
488    [crashParameters setObject:urlParameterValue
489                        forKey:urlParameterName];
490  }
491  return YES;
492}
493
494- (void)addServerParameter:(id)value forKey:(NSString *)key {
495  [extraServerVars_ setObject:value forKey:key];
496}
497
498//=============================================================================
499- (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error {
500  NSString *result = [[NSString alloc] initWithData:data
501                                           encoding:NSUTF8StringEncoding];
502  const char *reportID = "ERR";
503  if (error) {
504    fprintf(stderr, "Breakpad Uploader: Send Error: %s\n",
505            [[error description] UTF8String]);
506  } else {
507    NSCharacterSet *trimSet =
508        [NSCharacterSet whitespaceAndNewlineCharacterSet];
509    reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String];
510    [self logUploadWithID:reportID];
511  }
512  if (uploadCompletion_) {
513    uploadCompletion_([NSString stringWithUTF8String:reportID], error);
514  }
515
516  // rename the minidump file according to the id returned from the server
517  NSString *minidumpDir =
518      [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
519  NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
520
521  NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
522                                  minidumpDir, minidumpID];
523  NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
524                                   minidumpDir, reportID];
525
526  const char *src = [srcString fileSystemRepresentation];
527  const char *dest = [destString fileSystemRepresentation];
528
529  if (rename(src, dest) == 0) {
530    fprintf(stderr,
531            "Breakpad Uploader: Renamed %s to %s after successful upload", src,
532            dest);
533  }
534  else {
535    // can't rename - don't worry - it's not important for users
536    fprintf(stderr, "Breakpad Uploader: successful upload report ID = %s\n",
537            reportID);
538  }
539  [result release];
540}
541
542//=============================================================================
543- (NSURLQueryItem *)queryItemWithName:(NSString *)queryItemName
544                          forParamKey:(NSString *)key {
545  NSString *value = [parameters_ objectForKey:key];
546  NSString *escapedValue =
547    [value stringByAddingPercentEncodingWithAllowedCharacters:
548      [NSCharacterSet URLQueryAllowedCharacterSet]];
549  return [NSURLQueryItem queryItemWithName:queryItemName value:escapedValue];
550}
551
552//=============================================================================
553- (void)setUploadCompletionBlock:(UploadCompletionBlock)uploadCompletion {
554  uploadCompletion_ = uploadCompletion;
555}
556
557//=============================================================================
558- (void)report {
559  NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
560
561  NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE];
562  if ([serverType length] == 0 ||
563      [serverType isEqualToString:kGoogleServerType]) {
564    // when communicating to Google's crash collecting service, add URL params
565    // which identify the product
566    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url
567                                                resolvingAgainstBaseURL:false];
568    NSMutableArray *queryItemsToAdd = [urlComponents.queryItems mutableCopy];
569    if (queryItemsToAdd == nil) {
570      queryItemsToAdd = [[NSMutableArray alloc] init];
571    }
572
573    NSURLQueryItem *queryItemProduct =
574      [self queryItemWithName:@"product" forParamKey:@BREAKPAD_PRODUCT];
575    NSURLQueryItem *queryItemVersion =
576      [self queryItemWithName:@"version" forParamKey:@BREAKPAD_VERSION];
577    NSURLQueryItem *queryItemGuid =
578      [self queryItemWithName:@"guid" forParamKey:@"guid"];
579
580    if (queryItemProduct != nil) [queryItemsToAdd addObject:queryItemProduct];
581    if (queryItemVersion != nil) [queryItemsToAdd addObject:queryItemVersion];
582    if (queryItemGuid != nil) [queryItemsToAdd addObject:queryItemGuid];
583
584    urlComponents.queryItems = queryItemsToAdd;
585    url = [urlComponents URL];
586  }
587
588  HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
589  NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
590
591  if (![self populateServerDictionary:uploadParameters]) {
592    [upload release];
593    return;
594  }
595
596  [upload setParameters:uploadParameters];
597
598  // Add minidump file
599  if (minidumpContents_) {
600    [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
601
602    // If there is a log file, upload it together with the minidump.
603    if (logFileData_) {
604      [upload addFileContents:logFileData_ name:@"log"];
605    }
606
607    // Send it
608    NSError *error = nil;
609    NSData *data = [upload send:&error];
610
611    if (![url isFileURL]) {
612      [self handleNetworkResponse:data withError:error];
613    } else {
614      if (error) {
615        fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n",
616                [[error description] UTF8String]);
617      }
618    }
619
620  } else {
621    // Minidump is missing -- upload just the log file.
622    if (logFileData_) {
623      [self uploadData:logFileData_ name:@"log"];
624    }
625  }
626  [upload release];
627}
628
629- (void)uploadData:(NSData *)data name:(NSString *)name {
630  NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
631  NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
632
633  if (![self populateServerDictionary:uploadParameters])
634    return;
635
636  HTTPMultipartUpload *upload =
637      [[HTTPMultipartUpload alloc] initWithURL:url];
638
639  [uploadParameters setObject:name forKey:@"type"];
640  [upload setParameters:uploadParameters];
641  [upload addFileContents:data name:name];
642
643  [upload send:nil];
644  [upload release];
645}
646
647- (void)logUploadWithID:(const char *)uploadID {
648  NSString *minidumpDir =
649      [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
650  NSString *logFilePath = [NSString stringWithFormat:@"%@/%s",
651      minidumpDir, kReporterLogFilename];
652  NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n",
653      [[NSDate date] timeIntervalSince1970], uploadID];
654  NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding];
655
656  NSFileManager *fileManager = [NSFileManager defaultManager];
657  if ([fileManager fileExistsAtPath:logFilePath]) {
658    NSFileHandle *logFileHandle =
659       [NSFileHandle fileHandleForWritingAtPath:logFilePath];
660    [logFileHandle seekToEndOfFile];
661    [logFileHandle writeData:logData];
662    [logFileHandle closeFile];
663  } else {
664    [fileManager createFileAtPath:logFilePath
665                         contents:logData
666                       attributes:nil];
667  }
668}
669
670//=============================================================================
671- (NSMutableDictionary *)parameters {
672  return parameters_;
673}
674
675//=============================================================================
676- (void)dealloc {
677  [parameters_ release];
678  [minidumpContents_ release];
679  [logFileData_ release];
680  [googleDictionary_ release];
681  [socorroDictionary_ release];
682  [serverDictionary_ release];
683  [extraServerVars_ release];
684  [super dealloc];
685}
686
687@end
688